diff --git a/packages/kbn-securitysolution-list-utils/src/autocomplete_operators/index.ts b/packages/kbn-securitysolution-list-utils/src/autocomplete_operators/index.ts index 967cebc360f6..051c359dc461 100644 --- a/packages/kbn-securitysolution-list-utils/src/autocomplete_operators/index.ts +++ b/packages/kbn-securitysolution-list-utils/src/autocomplete_operators/index.ts @@ -11,8 +11,7 @@ import { ListOperatorEnum as OperatorEnum, ListOperatorTypeEnum as OperatorTypeEnum, } from '@kbn/securitysolution-io-ts-list-types'; - -import { OperatorOption } from './types'; +import { OperatorOption } from '../types'; export const isOperator: OperatorOption = { message: i18n.translate('lists.exceptions.isOperatorLabel', { diff --git a/packages/kbn-securitysolution-list-utils/src/autocomplete_operators/types.ts b/packages/kbn-securitysolution-list-utils/src/autocomplete_operators/types.ts deleted file mode 100644 index 1be21bb62a7f..000000000000 --- a/packages/kbn-securitysolution-list-utils/src/autocomplete_operators/types.ts +++ /dev/null @@ -1,19 +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 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import type { - ListOperatorEnum as OperatorEnum, - ListOperatorTypeEnum as OperatorTypeEnum, -} from '@kbn/securitysolution-io-ts-list-types'; - -export interface OperatorOption { - message: string; - value: string; - operator: OperatorEnum; - type: OperatorTypeEnum; -} diff --git a/packages/kbn-securitysolution-list-utils/src/helpers/index.ts b/packages/kbn-securitysolution-list-utils/src/helpers/index.ts index d208624b69fc..38446b2a08ec 100644 --- a/packages/kbn-securitysolution-list-utils/src/helpers/index.ts +++ b/packages/kbn-securitysolution-list-utils/src/helpers/index.ts @@ -43,7 +43,6 @@ import { isOneOfOperator, isOperator, } from '../autocomplete_operators'; -import { OperatorOption } from '../autocomplete_operators/types'; import { BuilderEntry, @@ -52,6 +51,7 @@ import { EmptyNestedEntry, ExceptionsBuilderExceptionItem, FormattedBuilderEntry, + OperatorOption, } from '../types'; export const isEntryNested = (item: BuilderEntry): item is EntryNested => { diff --git a/packages/kbn-securitysolution-list-utils/src/types/index.ts b/packages/kbn-securitysolution-list-utils/src/types/index.ts index faf68ca15798..537ac06a49f3 100644 --- a/packages/kbn-securitysolution-list-utils/src/types/index.ts +++ b/packages/kbn-securitysolution-list-utils/src/types/index.ts @@ -23,7 +23,12 @@ import { EXCEPTION_LIST_NAMESPACE_AGNOSTIC, } from '@kbn/securitysolution-list-constants'; -import type { OperatorOption } from '../autocomplete_operators/types'; +export interface OperatorOption { + message: string; + value: string; + operator: OperatorEnum; + type: OperatorTypeEnum; +} /** * @deprecated Use the one from core once it is in its own package which will be from: diff --git a/x-pack/plugins/lists/public/exceptions/components/autocomplete/field.tsx b/x-pack/plugins/lists/public/exceptions/components/autocomplete/field.tsx index b3a5e36f12e4..47527914e71f 100644 --- a/x-pack/plugins/lists/public/exceptions/components/autocomplete/field.tsx +++ b/x-pack/plugins/lists/public/exceptions/components/autocomplete/field.tsx @@ -28,6 +28,13 @@ interface OperatorProps { selectedField: IFieldType | undefined; } +/** + * There is a copy within: + * x-pack/plugins/security_solution/public/common/components/autocomplete/field.tsx + * + * TODO: This should be in its own packaged and not copied, https://github.com/elastic/kibana/issues/105378 + * NOTE: This has deviated from the copy and will have to be reconciled. + */ export const FieldComponent: React.FC = ({ fieldInputWidth, fieldTypeFilter = [], diff --git a/x-pack/plugins/lists/public/exceptions/components/autocomplete/field_value_match.tsx b/x-pack/plugins/lists/public/exceptions/components/autocomplete/field_value_match.tsx index c1776280842c..8dbe8f223ae5 100644 --- a/x-pack/plugins/lists/public/exceptions/components/autocomplete/field_value_match.tsx +++ b/x-pack/plugins/lists/public/exceptions/components/autocomplete/field_value_match.tsx @@ -47,6 +47,11 @@ interface AutocompleteFieldMatchProps { onError?: (arg: boolean) => void; } +/** + * There is a copy of this within: + * x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_match.tsx + * TODO: This should be in its own packaged and not copied, https://github.com/elastic/kibana/issues/105378 + */ export const AutocompleteFieldMatchComponent: React.FC = ({ placeholder, rowLabel, diff --git a/x-pack/plugins/lists/public/exceptions/components/autocomplete/helpers.ts b/x-pack/plugins/lists/public/exceptions/components/autocomplete/helpers.ts index 965214815eed..975416e27222 100644 --- a/x-pack/plugins/lists/public/exceptions/components/autocomplete/helpers.ts +++ b/x-pack/plugins/lists/public/exceptions/components/autocomplete/helpers.ts @@ -10,6 +10,7 @@ import { EuiComboBoxOptionOption } from '@elastic/eui'; import type { ListSchema, Type } from '@kbn/securitysolution-io-ts-list-types'; import { EXCEPTION_OPERATORS, + OperatorOption, doesNotExistOperator, existsOperator, isNotOperator, @@ -18,7 +19,7 @@ import { import { IFieldType } from '../../../../../../../src/plugins/data/common'; -import { GetGenericComboBoxPropsReturn, OperatorOption } from './types'; +import { GetGenericComboBoxPropsReturn } from './types'; import * as i18n from './translations'; /** @@ -72,6 +73,10 @@ export const checkEmptyValue = ( /** * Very basic validation for values + * There is a copy within: + * x-pack/plugins/security_solution/public/common/components/autocomplete/helpers.ts + * + * TODO: This should be in its own packaged and not copied, https://github.com/elastic/kibana/issues/105378 * * @param param the value being checked * @param field the selected field @@ -109,7 +114,10 @@ export const paramIsValid = ( /** * Determines the options, selected values and option labels for EUI combo box + * There is a copy within: + * x-pack/plugins/security_solution/public/common/components/autocomplete/helpers.ts * + * TODO: This should be in its own packaged and not copied, https://github.com/elastic/kibana/issues/105378 * @param options options user can select from * @param selectedOptions user selection if any * @param getLabel helper function to know which property to use for labels diff --git a/x-pack/plugins/lists/public/exceptions/components/autocomplete/hooks/use_field_value_autocomplete.ts b/x-pack/plugins/lists/public/exceptions/components/autocomplete/hooks/use_field_value_autocomplete.ts index 674bb5e5537d..63d3925d6d64 100644 --- a/x-pack/plugins/lists/public/exceptions/components/autocomplete/hooks/use_field_value_autocomplete.ts +++ b/x-pack/plugins/lists/public/exceptions/components/autocomplete/hooks/use_field_value_autocomplete.ts @@ -33,7 +33,10 @@ export interface UseFieldValueAutocompleteProps { } /** * Hook for using the field value autocomplete service + * There is a copy within: + * x-pack/plugins/security_solution/public/common/components/autocomplete/hooks/use_field_value_autocomplete.ts * + * TODO: This should be in its own packaged and not copied, https://github.com/elastic/kibana/issues/105378 */ export const useFieldValueAutocomplete = ({ selectedField, diff --git a/x-pack/plugins/lists/public/exceptions/components/autocomplete/operator.tsx b/x-pack/plugins/lists/public/exceptions/components/autocomplete/operator.tsx index 7fc221c5a097..0d2fe5bd664b 100644 --- a/x-pack/plugins/lists/public/exceptions/components/autocomplete/operator.tsx +++ b/x-pack/plugins/lists/public/exceptions/components/autocomplete/operator.tsx @@ -7,11 +7,12 @@ import React, { useCallback, useMemo } from 'react'; import { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui'; +import { OperatorOption } from '@kbn/securitysolution-list-utils'; import { IFieldType } from '../../../../../../../src/plugins/data/common'; import { getGenericComboBoxProps, getOperators } from './helpers'; -import { GetGenericComboBoxPropsReturn, OperatorOption } from './types'; +import { GetGenericComboBoxPropsReturn } from './types'; const AS_PLAIN_TEXT = { asPlainText: true }; diff --git a/x-pack/plugins/lists/public/exceptions/components/autocomplete/types.ts b/x-pack/plugins/lists/public/exceptions/components/autocomplete/types.ts index 76d5b7758007..07f1903fb70e 100644 --- a/x-pack/plugins/lists/public/exceptions/components/autocomplete/types.ts +++ b/x-pack/plugins/lists/public/exceptions/components/autocomplete/types.ts @@ -6,20 +6,9 @@ */ import { EuiComboBoxOptionOption } from '@elastic/eui'; -import type { - ListOperatorEnum as OperatorEnum, - ListOperatorTypeEnum as OperatorTypeEnum, -} from '@kbn/securitysolution-io-ts-list-types'; export interface GetGenericComboBoxPropsReturn { comboOptions: EuiComboBoxOptionOption[]; labels: string[]; selectedComboOptions: EuiComboBoxOptionOption[]; } - -export interface OperatorOption { - message: string; - value: string; - operator: OperatorEnum; - type: OperatorTypeEnum; -} diff --git a/x-pack/plugins/lists/public/exceptions/components/builder/entry_renderer.tsx b/x-pack/plugins/lists/public/exceptions/components/builder/entry_renderer.tsx index 7daef8467dd1..c54da89766d7 100644 --- a/x-pack/plugins/lists/public/exceptions/components/builder/entry_renderer.tsx +++ b/x-pack/plugins/lists/public/exceptions/components/builder/entry_renderer.tsx @@ -18,6 +18,7 @@ import { BuilderEntry, EXCEPTION_OPERATORS_ONLY_LISTS, FormattedBuilderEntry, + OperatorOption, getEntryOnFieldChange, getEntryOnListChange, getEntryOnMatchAnyChange, @@ -32,7 +33,6 @@ import { IFieldType, IIndexPattern } from '../../../../../../../src/plugins/data import { HttpStart } from '../../../../../../../src/core/public'; import { FieldComponent } from '../autocomplete/field'; import { OperatorComponent } from '../autocomplete/operator'; -import { OperatorOption } from '../autocomplete/types'; import { AutocompleteFieldExistsComponent } from '../autocomplete/field_value_exists'; import { AutocompleteFieldMatchComponent } from '../autocomplete/field_value_match'; import { AutocompleteFieldMatchAnyComponent } from '../autocomplete/field_value_match_any'; diff --git a/x-pack/plugins/lists/public/exceptions/components/builder/helpers.test.ts b/x-pack/plugins/lists/public/exceptions/components/builder/helpers.test.ts index 212db40f3168..afeac2d1bf4d 100644 --- a/x-pack/plugins/lists/public/exceptions/components/builder/helpers.test.ts +++ b/x-pack/plugins/lists/public/exceptions/components/builder/helpers.test.ts @@ -24,6 +24,7 @@ import { EmptyEntry, ExceptionsBuilderExceptionItem, FormattedBuilderEntry, + OperatorOption, doesNotExistOperator, existsOperator, filterExceptionItems, @@ -64,7 +65,6 @@ import { getEntryNestedMock } from '../../../../common/schemas/types/entry_neste import { getEntryMatchMock } from '../../../../common/schemas/types/entry_match.mock'; import { getEntryMatchAnyMock } from '../../../../common/schemas/types/entry_match_any.mock'; import { getListResponseMock } from '../../../../common/schemas/response/list_schema.mock'; -import { OperatorOption } from '../autocomplete/types'; import { getEntryListMock } from '../../../../common/schemas/types/entry_list.mock'; // TODO: ALL THESE TESTS SHOULD BE MOVED TO @kbn/securitysolution-list-utils for its helper. The only reason why they're here is due to missing other packages we hae to create or missing things from kbn packages such as mocks from kibana core diff --git a/x-pack/plugins/security_solution/public/common/components/autocomplete/field.tsx b/x-pack/plugins/security_solution/public/common/components/autocomplete/field.tsx index d4185fe63969..a175a9b847c7 100644 --- a/x-pack/plugins/security_solution/public/common/components/autocomplete/field.tsx +++ b/x-pack/plugins/security_solution/public/common/components/autocomplete/field.tsx @@ -25,6 +25,13 @@ interface OperatorProps { onChange: (a: IFieldType[]) => void; } +/** + * There is a copy within: + * x-pack/plugins/lists/public/exceptions/components/autocomplete/field.tsx + * + * TODO: This should be in its own packaged and not copied, https://github.com/elastic/kibana/issues/105378 + * NOTE: This has deviated from the copy and will have to be reconciled. + */ export const FieldComponent: React.FC = ({ placeholder, selectedField, diff --git a/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_exists.test.tsx b/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_exists.test.tsx deleted file mode 100644 index b6300581f12d..000000000000 --- a/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_exists.test.tsx +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { mount } from 'enzyme'; - -import { AutocompleteFieldExistsComponent } from './field_value_exists'; - -describe('AutocompleteFieldExistsComponent', () => { - test('it renders field disabled', () => { - const wrapper = mount(); - - expect( - wrapper - .find(`[data-test-subj="valuesAutocompleteComboBox existsComboxBox"] input`) - .prop('disabled') - ).toBeTruthy(); - }); -}); diff --git a/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_exists.tsx b/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_exists.tsx deleted file mode 100644 index 715ba5270117..000000000000 --- a/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_exists.tsx +++ /dev/null @@ -1,33 +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 { EuiFormRow, EuiComboBox } from '@elastic/eui'; - -interface AutocompleteFieldExistsProps { - placeholder: string; - rowLabel?: string; -} - -export const AutocompleteFieldExistsComponent: React.FC = ({ - placeholder, - rowLabel, -}): JSX.Element => ( - - - -); - -AutocompleteFieldExistsComponent.displayName = 'AutocompleteFieldExists'; diff --git a/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_lists.test.tsx b/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_lists.test.tsx deleted file mode 100644 index 164b8e8d2a6d..000000000000 --- a/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_lists.test.tsx +++ /dev/null @@ -1,214 +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 { mount } from 'enzyme'; -import { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui'; -import { waitFor } from '@testing-library/react'; - -import { getField } from '../../../../../../../src/plugins/data/common/index_patterns/fields/fields.mocks'; -import type { ListSchema } from '@kbn/securitysolution-io-ts-list-types'; -import { getFoundListSchemaMock } from '../../../../../lists/common/schemas/response/found_list_schema.mock'; -import { getListResponseMock } from '../../../../../lists/common/schemas/response/list_schema.mock'; -import { DATE_NOW, VERSION, IMMUTABLE } from '../../../../../lists/common/constants.mock'; - -import { AutocompleteFieldListsComponent } from './field_value_lists'; - -jest.mock('../../../common/lib/kibana'); -const mockStart = jest.fn(); -const mockKeywordList: ListSchema = { - ...getListResponseMock(), - id: 'keyword_list', - type: 'keyword', - name: 'keyword list', -}; -const mockResult = { ...getFoundListSchemaMock() }; -mockResult.data = [...mockResult.data, mockKeywordList]; -jest.mock('@kbn/securitysolution-list-hooks', () => { - const originalModule = jest.requireActual('@kbn/securitysolution-list-hooks'); - - return { - ...originalModule, - useFindLists: () => ({ - loading: false, - start: mockStart.mockReturnValue(mockResult), - result: mockResult, - error: undefined, - }), - }; -}); - -describe('AutocompleteFieldListsComponent', () => { - test('it renders disabled if "isDisabled" is true', async () => { - const wrapper = mount( - - ); - - expect( - wrapper - .find(`[data-test-subj="valuesAutocompleteComboBox listsComboxBox"] input`) - .prop('disabled') - ).toBeTruthy(); - }); - - test('it renders loading if "isLoading" is true', async () => { - const wrapper = mount( - - ); - - wrapper - .find(`[data-test-subj="valuesAutocompleteComboBox listsComboxBox"] button`) - .at(0) - .simulate('click'); - expect( - wrapper - .find( - `EuiComboBoxOptionsList[data-test-subj="valuesAutocompleteComboBox listsComboxBox-optionsList"]` - ) - .prop('isLoading') - ).toBeTruthy(); - }); - - test('it allows user to clear values if "isClearable" is true', async () => { - const wrapper = mount( - - ); - expect( - wrapper - .find('EuiComboBox[data-test-subj="valuesAutocompleteComboBox listsComboxBox"]') - .prop('options') - ).toEqual([{ label: 'some name' }]); - }); - - test('it correctly displays lists that match the selected "keyword" field esType', () => { - const wrapper = mount( - - ); - - wrapper.find('[data-test-subj="comboBoxToggleListButton"] button').simulate('click'); - - expect( - wrapper - .find('EuiComboBox[data-test-subj="valuesAutocompleteComboBox listsComboxBox"]') - .prop('options') - ).toEqual([{ label: 'keyword list' }]); - }); - - test('it correctly displays lists that match the selected "ip" field esType', () => { - const wrapper = mount( - - ); - - wrapper.find('[data-test-subj="comboBoxToggleListButton"] button').simulate('click'); - - expect( - wrapper - .find('EuiComboBox[data-test-subj="valuesAutocompleteComboBox listsComboxBox"]') - .prop('options') - ).toEqual([{ label: 'some name' }]); - }); - - test('it correctly displays selected list', async () => { - const wrapper = mount( - - ); - - expect( - wrapper - .find(`[data-test-subj="valuesAutocompleteComboBox listsComboxBox"] EuiComboBoxPill`) - .at(0) - .text() - ).toEqual('some name'); - }); - - test('it invokes "onChange" when option selected', async () => { - const mockOnChange = jest.fn(); - const wrapper = mount( - - ); - - ((wrapper.find(EuiComboBox).props() as unknown) as { - onChange: (a: EuiComboBoxOptionOption[]) => void; - }).onChange([{ label: 'some name' }]); - - await waitFor(() => { - expect(mockOnChange).toHaveBeenCalledWith({ - created_at: DATE_NOW, - created_by: 'some user', - description: 'some description', - id: 'some-list-id', - meta: {}, - name: 'some name', - tie_breaker_id: '6a76b69d-80df-4ab2-8c3e-85f466b06a0e', - type: 'ip', - updated_at: DATE_NOW, - updated_by: 'some user', - _version: undefined, - version: VERSION, - deserializer: undefined, - serializer: undefined, - immutable: IMMUTABLE, - }); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_lists.tsx b/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_lists.tsx deleted file mode 100644 index e8a3c2e70c75..000000000000 --- a/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_lists.tsx +++ /dev/null @@ -1,121 +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, { useState, useEffect, useCallback, useMemo } from 'react'; -import { EuiFormRow, EuiComboBoxOptionOption, EuiComboBox } from '@elastic/eui'; - -import type { ListSchema } from '@kbn/securitysolution-io-ts-list-types'; -import { useFindLists } from '@kbn/securitysolution-list-hooks'; -import { IFieldType } from '../../../../../../../src/plugins/data/common'; -import { useKibana } from '../../../common/lib/kibana'; -import { filterFieldToList, getGenericComboBoxProps } from './helpers'; -import * as i18n from './translations'; - -interface AutocompleteFieldListsProps { - placeholder: string; - selectedField: IFieldType | undefined; - selectedValue: string | undefined; - isLoading: boolean; - isDisabled: boolean; - isClearable: boolean; - isRequired?: boolean; - rowLabel?: string; - onChange: (arg: ListSchema) => void; -} - -export const AutocompleteFieldListsComponent: React.FC = ({ - placeholder, - rowLabel, - selectedField, - selectedValue, - isLoading = false, - isDisabled = false, - isClearable = false, - isRequired = false, - onChange, -}): JSX.Element => { - const [error, setError] = useState(undefined); - const { http } = useKibana().services; - const [lists, setLists] = useState([]); - const { loading, result, start } = useFindLists(); - const getLabel = useCallback(({ name }) => name, []); - - const optionsMemo = useMemo(() => filterFieldToList(lists, selectedField), [ - lists, - selectedField, - ]); - const selectedOptionsMemo = useMemo(() => { - if (selectedValue != null) { - const list = lists.filter(({ id }) => id === selectedValue); - return list ?? []; - } else { - return []; - } - }, [selectedValue, lists]); - const { comboOptions, labels, selectedComboOptions } = useMemo( - () => - getGenericComboBoxProps({ - options: optionsMemo, - selectedOptions: selectedOptionsMemo, - getLabel, - }), - [optionsMemo, selectedOptionsMemo, getLabel] - ); - - const handleValuesChange = useCallback( - (newOptions: EuiComboBoxOptionOption[]) => { - const [newValue] = newOptions.map(({ label }) => optionsMemo[labels.indexOf(label)]); - onChange(newValue ?? ''); - }, - [labels, optionsMemo, onChange] - ); - - const setIsTouchedValue = useCallback((): void => { - setError(selectedValue == null ? i18n.FIELD_REQUIRED_ERR : undefined); - }, [selectedValue]); - - useEffect(() => { - if (result != null) { - setLists(result.data); - } - }, [result]); - - useEffect(() => { - if (selectedField != null) { - start({ - http, - pageIndex: 1, - pageSize: 500, - }); - } - }, [selectedField, start, http]); - - const isLoadingState = useMemo((): boolean => isLoading || loading, [isLoading, loading]); - - return ( - - - - ); -}; - -AutocompleteFieldListsComponent.displayName = 'AutocompleteFieldList'; diff --git a/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_match.tsx b/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_match.tsx index 9cb219e7a8d4..21d1d9b4b31a 100644 --- a/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_match.tsx +++ b/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_match.tsx @@ -38,6 +38,11 @@ interface AutocompleteFieldMatchProps { onError?: (arg: boolean) => void; } +/** + * There is a copy of this within: + * x-pack/plugins/lists/public/exceptions/components/autocomplete/field_value_match.tsx + * TODO: This should be in its own packaged and not copied, https://github.com/elastic/kibana/issues/105378 + */ export const AutocompleteFieldMatchComponent: React.FC = ({ placeholder, rowLabel, diff --git a/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_match_any.test.tsx b/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_match_any.test.tsx deleted file mode 100644 index 6b479c5ab8c4..000000000000 --- a/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_match_any.test.tsx +++ /dev/null @@ -1,238 +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 { mount, ReactWrapper } from 'enzyme'; -import { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui'; -import { act } from '@testing-library/react'; - -import { - fields, - getField, -} from '../../../../../../../src/plugins/data/common/index_patterns/fields/fields.mocks'; -import { AutocompleteFieldMatchAnyComponent } from './field_value_match_any'; -import { useFieldValueAutocomplete } from './hooks/use_field_value_autocomplete'; - -jest.mock('./hooks/use_field_value_autocomplete'); - -describe('AutocompleteFieldMatchAnyComponent', () => { - let wrapper: ReactWrapper; - const getValueSuggestionsMock = jest - .fn() - .mockResolvedValue([false, true, ['value 3', 'value 4'], jest.fn()]); - - beforeEach(() => { - (useFieldValueAutocomplete as jest.Mock).mockReturnValue([ - false, - true, - ['value 1', 'value 2'], - getValueSuggestionsMock, - ]); - }); - - afterEach(() => { - jest.clearAllMocks(); - wrapper.unmount(); - }); - - test('it renders disabled if "isDisabled" is true', () => { - wrapper = mount( - - ); - - expect( - wrapper.find(`[data-test-subj="valuesAutocompleteMatchAny"] input`).prop('disabled') - ).toBeTruthy(); - }); - - test('it renders loading if "isLoading" is true', () => { - wrapper = mount( - - ); - wrapper.find(`[data-test-subj="valuesAutocompleteMatchAny"] button`).at(0).simulate('click'); - expect( - wrapper - .find(`EuiComboBoxOptionsList[data-test-subj="valuesAutocompleteMatchAny-optionsList"]`) - .prop('isLoading') - ).toBeTruthy(); - }); - - test('it allows user to clear values if "isClearable" is true', () => { - wrapper = mount( - - ); - - expect( - wrapper - .find(`[data-test-subj="comboBoxInput"]`) - .hasClass('euiComboBox__inputWrap-isClearable') - ).toBeTruthy(); - }); - - test('it correctly displays selected value', () => { - wrapper = mount( - - ); - - expect( - wrapper.find(`[data-test-subj="valuesAutocompleteMatchAny"] EuiComboBoxPill`).at(0).text() - ).toEqual('126.45.211.34'); - }); - - test('it invokes "onChange" when new value created', async () => { - const mockOnChange = jest.fn(); - wrapper = mount( - - ); - - ((wrapper.find(EuiComboBox).props() as unknown) as { - onCreateOption: (a: string) => void; - }).onCreateOption('126.45.211.34'); - - expect(mockOnChange).toHaveBeenCalledWith(['126.45.211.34']); - }); - - test('it invokes "onChange" when new value selected', async () => { - const mockOnChange = jest.fn(); - wrapper = mount( - - ); - - ((wrapper.find(EuiComboBox).props() as unknown) as { - onChange: (a: EuiComboBoxOptionOption[]) => void; - }).onChange([{ label: 'value 1' }]); - - expect(mockOnChange).toHaveBeenCalledWith(['value 1']); - }); - - test('it refreshes autocomplete with search query when new value searched', () => { - wrapper = mount( - - ); - act(() => { - ((wrapper.find(EuiComboBox).props() as unknown) as { - onSearchChange: (a: string) => void; - }).onSearchChange('value 1'); - }); - - expect(useFieldValueAutocomplete).toHaveBeenCalledWith({ - selectedField: getField('machine.os.raw'), - operatorType: 'match_any', - query: 'value 1', - fieldValue: [], - indexPattern: { - id: '1234', - title: 'logstash-*', - fields, - }, - }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_match_any.tsx b/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_match_any.tsx deleted file mode 100644 index dbfdaf9749b6..000000000000 --- a/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_match_any.tsx +++ /dev/null @@ -1,221 +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, { useState, useCallback, useMemo } from 'react'; -import { EuiFormRow, EuiComboBoxOptionOption, EuiComboBox } from '@elastic/eui'; -import { uniq } from 'lodash'; - -import { ListOperatorTypeEnum as OperatorTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; -import { IFieldType, IIndexPattern } from '../../../../../../../src/plugins/data/common'; -import { useFieldValueAutocomplete } from './hooks/use_field_value_autocomplete'; -import { getGenericComboBoxProps, paramIsValid } from './helpers'; -import { GetGenericComboBoxPropsReturn } from './types'; - -import * as i18n from './translations'; - -interface AutocompleteFieldMatchAnyProps { - placeholder: string; - selectedField: IFieldType | undefined; - selectedValue: string[]; - indexPattern: IIndexPattern | undefined; - isLoading: boolean; - isDisabled: boolean; - isClearable: boolean; - isRequired?: boolean; - rowLabel?: string; - onChange: (arg: string[]) => void; - onError?: (arg: boolean) => void; -} - -export const AutocompleteFieldMatchAnyComponent: React.FC = ({ - placeholder, - rowLabel, - selectedField, - selectedValue, - indexPattern, - isLoading, - isDisabled = false, - isClearable = false, - isRequired = false, - onChange, - onError, -}): JSX.Element => { - const [searchQuery, setSearchQuery] = useState(''); - const [touched, setIsTouched] = useState(false); - const [error, setError] = useState(undefined); - const [isLoadingSuggestions, isSuggestingValues, suggestions] = useFieldValueAutocomplete({ - selectedField, - operatorType: OperatorTypeEnum.MATCH_ANY, - fieldValue: selectedValue, - query: searchQuery, - indexPattern, - }); - const getLabel = useCallback((option: string): string => option, []); - const optionsMemo = useMemo( - (): string[] => (selectedValue ? uniq([...selectedValue, ...suggestions]) : suggestions), - [suggestions, selectedValue] - ); - const { comboOptions, labels, selectedComboOptions } = useMemo( - (): GetGenericComboBoxPropsReturn => - getGenericComboBoxProps({ - options: optionsMemo, - selectedOptions: selectedValue, - getLabel, - }), - [optionsMemo, selectedValue, getLabel] - ); - - const handleError = useCallback( - (err: string | undefined): void => { - setError((existingErr): string | undefined => { - const oldErr = existingErr != null; - const newErr = err != null; - if (oldErr !== newErr && onError != null) { - onError(newErr); - } - - return err; - }); - }, - [setError, onError] - ); - - const handleValuesChange = useCallback( - (newOptions: EuiComboBoxOptionOption[]): void => { - const newValues: string[] = newOptions.map(({ label }) => optionsMemo[labels.indexOf(label)]); - handleError(undefined); - onChange(newValues); - }, - [handleError, labels, onChange, optionsMemo] - ); - - const handleSearchChange = useCallback( - (searchVal: string) => { - if (searchVal === '') { - handleError(undefined); - } - - if (searchVal !== '' && selectedField != null) { - const err = paramIsValid(searchVal, selectedField, isRequired, touched); - handleError(err); - - setSearchQuery(searchVal); - } - }, - [handleError, isRequired, selectedField, touched] - ); - - const handleCreateOption = useCallback( - (option: string): boolean | void => { - const err = paramIsValid(option, selectedField, isRequired, touched); - handleError(err); - - if (err != null) { - // Explicitly reject the user's input - return false; - } else { - onChange([...(selectedValue || []), option]); - } - }, - [handleError, isRequired, onChange, selectedField, selectedValue, touched] - ); - - const setIsTouchedValue = useCallback((): void => { - handleError(selectedComboOptions.length === 0 ? i18n.FIELD_REQUIRED_ERR : undefined); - setIsTouched(true); - }, [setIsTouched, handleError, selectedComboOptions]); - - const inputPlaceholder = useMemo( - (): string => (isLoading || isLoadingSuggestions ? i18n.LOADING : placeholder), - [isLoading, isLoadingSuggestions, placeholder] - ); - - const isLoadingState = useMemo((): boolean => isLoading || isLoadingSuggestions, [ - isLoading, - isLoadingSuggestions, - ]); - - const defaultInput = useMemo((): JSX.Element => { - return ( - - - - ); - }, [ - comboOptions, - error, - handleCreateOption, - handleSearchChange, - handleValuesChange, - inputPlaceholder, - isClearable, - isDisabled, - isLoadingState, - rowLabel, - selectedComboOptions, - selectedField, - setIsTouchedValue, - ]); - - if (!isSuggestingValues && selectedField != null) { - switch (selectedField.type) { - case 'number': - return ( - - - - ); - default: - return defaultInput; - } - } - - return defaultInput; -}; - -AutocompleteFieldMatchAnyComponent.displayName = 'AutocompleteFieldMatchAny'; diff --git a/x-pack/plugins/security_solution/public/common/components/autocomplete/helpers.test.ts b/x-pack/plugins/security_solution/public/common/components/autocomplete/helpers.test.ts index ae695bf7be97..1618de245365 100644 --- a/x-pack/plugins/security_solution/public/common/components/autocomplete/helpers.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/autocomplete/helpers.test.ts @@ -8,65 +8,13 @@ import moment from 'moment'; import '../../../common/mock/match_media'; import { getField } from '../../../../../../../src/plugins/data/common/index_patterns/fields/fields.mocks'; -import { IFieldType } from '../../../../../../../src/plugins/data/common'; import * as i18n from './translations'; -import { - EXCEPTION_OPERATORS, - isOperator, - isNotOperator, - existsOperator, - doesNotExistOperator, -} from '@kbn/securitysolution-list-utils'; -import { - getOperators, - checkEmptyValue, - paramIsValid, - getGenericComboBoxProps, - typeMatch, - filterFieldToList, -} from './helpers'; -import { getListResponseMock } from '../../../../../lists/common/schemas/response/list_schema.mock'; -import type { ListSchema } from '@kbn/securitysolution-io-ts-list-types'; +import { checkEmptyValue, paramIsValid, getGenericComboBoxProps } from './helpers'; describe('helpers', () => { // @ts-ignore moment.suppressDeprecationWarnings = true; - describe('#getOperators', () => { - test('it returns "isOperator" if passed in field is "undefined"', () => { - const operator = getOperators(undefined); - - expect(operator).toEqual([isOperator]); - }); - - test('it returns expected operators when field type is "boolean"', () => { - const operator = getOperators(getField('ssl')); - - expect(operator).toEqual([isOperator, isNotOperator, existsOperator, doesNotExistOperator]); - }); - - test('it returns "isOperator" when field type is "nested"', () => { - const operator = getOperators({ - name: 'nestedField', - type: 'nested', - esTypes: ['text'], - count: 0, - scripted: false, - searchable: true, - aggregatable: false, - readFromDocValues: false, - subType: { nested: { path: 'nestedField' } }, - }); - - expect(operator).toEqual([isOperator]); - }); - - test('it returns all operator types when field type is not null, boolean, or nested', () => { - const operator = getOperators(getField('machine.os.raw')); - - expect(operator).toEqual(EXCEPTION_OPERATORS); - }); - }); describe('#checkEmptyValue', () => { test('returns no errors if no field has been selected', () => { @@ -272,117 +220,4 @@ describe('helpers', () => { }); }); }); - - describe('#typeMatch', () => { - test('ip -> ip is true', () => { - expect(typeMatch('ip', 'ip')).toEqual(true); - }); - - test('keyword -> keyword is true', () => { - expect(typeMatch('keyword', 'keyword')).toEqual(true); - }); - - test('text -> text is true', () => { - expect(typeMatch('text', 'text')).toEqual(true); - }); - - test('ip_range -> ip is true', () => { - expect(typeMatch('ip_range', 'ip')).toEqual(true); - }); - - test('date_range -> date is true', () => { - expect(typeMatch('date_range', 'date')).toEqual(true); - }); - - test('double_range -> double is true', () => { - expect(typeMatch('double_range', 'double')).toEqual(true); - }); - - test('float_range -> float is true', () => { - expect(typeMatch('float_range', 'float')).toEqual(true); - }); - - test('integer_range -> integer is true', () => { - expect(typeMatch('integer_range', 'integer')).toEqual(true); - }); - - test('long_range -> long is true', () => { - expect(typeMatch('long_range', 'long')).toEqual(true); - }); - - test('ip -> date is false', () => { - expect(typeMatch('ip', 'date')).toEqual(false); - }); - - test('long -> float is false', () => { - expect(typeMatch('long', 'float')).toEqual(false); - }); - - test('integer -> long is false', () => { - expect(typeMatch('integer', 'long')).toEqual(false); - }); - }); - - describe('#filterFieldToList', () => { - test('it returns empty array if given a undefined for field', () => { - const filter = filterFieldToList([], undefined); - expect(filter).toEqual([]); - }); - - test('it returns empty array if filed does not contain esTypes', () => { - const field: IFieldType = { name: 'some-name', type: 'some-type' }; - const filter = filterFieldToList([], field); - expect(filter).toEqual([]); - }); - - test('it returns single filtered list of ip_range -> ip', () => { - const field: IFieldType = { name: 'some-name', type: 'ip', esTypes: ['ip'] }; - const listItem: ListSchema = { ...getListResponseMock(), type: 'ip_range' }; - const filter = filterFieldToList([listItem], field); - const expected: ListSchema[] = [listItem]; - expect(filter).toEqual(expected); - }); - - test('it returns single filtered list of ip -> ip', () => { - const field: IFieldType = { name: 'some-name', type: 'ip', esTypes: ['ip'] }; - const listItem: ListSchema = { ...getListResponseMock(), type: 'ip' }; - const filter = filterFieldToList([listItem], field); - const expected: ListSchema[] = [listItem]; - expect(filter).toEqual(expected); - }); - - test('it returns single filtered list of keyword -> keyword', () => { - const field: IFieldType = { name: 'some-name', type: 'keyword', esTypes: ['keyword'] }; - const listItem: ListSchema = { ...getListResponseMock(), type: 'keyword' }; - const filter = filterFieldToList([listItem], field); - const expected: ListSchema[] = [listItem]; - expect(filter).toEqual(expected); - }); - - test('it returns single filtered list of text -> text', () => { - const field: IFieldType = { name: 'some-name', type: 'text', esTypes: ['text'] }; - const listItem: ListSchema = { ...getListResponseMock(), type: 'text' }; - const filter = filterFieldToList([listItem], field); - const expected: ListSchema[] = [listItem]; - expect(filter).toEqual(expected); - }); - - test('it returns 2 filtered lists of ip_range -> ip', () => { - const field: IFieldType = { name: 'some-name', type: 'ip', esTypes: ['ip'] }; - const listItem1: ListSchema = { ...getListResponseMock(), type: 'ip_range' }; - const listItem2: ListSchema = { ...getListResponseMock(), type: 'ip_range' }; - const filter = filterFieldToList([listItem1, listItem2], field); - const expected: ListSchema[] = [listItem1, listItem2]; - expect(filter).toEqual(expected); - }); - - test('it returns 1 filtered lists of ip_range -> ip if the 2nd is not compatible type', () => { - const field: IFieldType = { name: 'some-name', type: 'ip', esTypes: ['ip'] }; - const listItem1: ListSchema = { ...getListResponseMock(), type: 'ip_range' }; - const listItem2: ListSchema = { ...getListResponseMock(), type: 'text' }; - const filter = filterFieldToList([listItem1, listItem2], field); - const expected: ListSchema[] = [listItem1]; - expect(filter).toEqual(expected); - }); - }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/autocomplete/helpers.ts b/x-pack/plugins/security_solution/public/common/components/autocomplete/helpers.ts index 81f5a6623856..890f1e675583 100644 --- a/x-pack/plugins/security_solution/public/common/components/autocomplete/helpers.ts +++ b/x-pack/plugins/security_solution/public/common/components/autocomplete/helpers.ts @@ -8,46 +8,17 @@ import dateMath from '@elastic/datemath'; import { EuiComboBoxOptionOption } from '@elastic/eui'; -import type { Type, ListSchema } from '@kbn/securitysolution-io-ts-list-types'; -import { - EXCEPTION_OPERATORS, - isOperator, - isNotOperator, - existsOperator, - doesNotExistOperator, -} from '@kbn/securitysolution-list-utils'; import { IFieldType } from '../../../../../../../src/plugins/data/common'; -import { GetGenericComboBoxPropsReturn, OperatorOption } from './types'; +import { GetGenericComboBoxPropsReturn } from './types'; import * as i18n from './translations'; -/** - * Returns the appropriate operators given a field type - * - * @param field IFieldType selected field - * - */ -export const getOperators = (field: IFieldType | undefined): OperatorOption[] => { - if (field == null) { - return [isOperator]; - } else if (field.type === 'boolean') { - return [isOperator, isNotOperator, existsOperator, doesNotExistOperator]; - } else if (field.type === 'nested') { - return [isOperator]; - } else { - return EXCEPTION_OPERATORS; - } -}; - /** * Determines if empty value is ok + * There is a copy within: + * x-pack/plugins/lists/public/exceptions/components/autocomplete/helpers.ts * - * @param param the value being checked - * @param field the selected field - * @param isRequired whether or not an empty value is allowed - * @param touched has field been touched by user - * @returns undefined if valid, string with error message if invalid, - * null if no checks matched + * TODO: This should be in its own packaged and not copied, https://github.com/elastic/kibana/issues/105378 */ export const checkEmptyValue = ( param: string | undefined, @@ -72,7 +43,10 @@ export const checkEmptyValue = ( /** * Very basic validation for values + * There is a copy within: + * x-pack/plugins/lists/public/exceptions/components/autocomplete/helpers.ts * + * TODO: This should be in its own packaged and not copied, https://github.com/elastic/kibana/issues/105378 * @param param the value being checked * @param field the selected field * @param isRequired whether or not an empty value is allowed @@ -109,7 +83,10 @@ export const paramIsValid = ( /** * Determines the options, selected values and option labels for EUI combo box + * There is a copy within: + * x-pack/plugins/lists/public/exceptions/components/autocomplete/helpers.ts * + * TODO: This should be in its own packaged and not copied, https://github.com/elastic/kibana/issues/105378 * @param options options user can select from * @param selectedOptions user selection if any * @param getLabel helper function to know which property to use for labels @@ -140,36 +117,3 @@ export function getGenericComboBoxProps({ selectedComboOptions: newSelectedComboOptions, }; } - -/** - * Given an array of lists and optionally a field this will return all - * the lists that match against the field based on the types from the field - * @param lists The lists to match against the field - * @param field The field to check against the list to see if they are compatible - */ -export const filterFieldToList = (lists: ListSchema[], field?: IFieldType): ListSchema[] => { - if (field != null) { - const { esTypes = [] } = field; - return lists.filter(({ type }) => esTypes.some((esType) => typeMatch(type, esType))); - } else { - return []; - } -}; - -/** - * Given an input list type and a string based ES type this will match - * if they're exact or if they are compatible with a range - * @param type The type to match against the esType - * @param esType The ES type to match with - */ -export const typeMatch = (type: Type, esType: string): boolean => { - return ( - type === esType || - (type === 'ip_range' && esType === 'ip') || - (type === 'date_range' && esType === 'date') || - (type === 'double_range' && esType === 'double') || - (type === 'float_range' && esType === 'float') || - (type === 'integer_range' && esType === 'integer') || - (type === 'long_range' && esType === 'long') - ); -}; diff --git a/x-pack/plugins/security_solution/public/common/components/autocomplete/hooks/use_field_value_autocomplete.ts b/x-pack/plugins/security_solution/public/common/components/autocomplete/hooks/use_field_value_autocomplete.ts index 0f369fa01d01..0fc4a663b7e1 100644 --- a/x-pack/plugins/security_solution/public/common/components/autocomplete/hooks/use_field_value_autocomplete.ts +++ b/x-pack/plugins/security_solution/public/common/components/autocomplete/hooks/use_field_value_autocomplete.ts @@ -30,9 +30,13 @@ export interface UseFieldValueAutocompleteProps { query: string; indexPattern: IIndexPattern | undefined; } + /** * Hook for using the field value autocomplete service + * There is a copy within: + * x-pack/plugins/lists/public/exceptions/components/autocomplete/hooks.ts * + * TODO: This should be in its own packaged and not copied, https://github.com/elastic/kibana/issues/105378 */ export const useFieldValueAutocomplete = ({ selectedField, diff --git a/x-pack/plugins/security_solution/public/common/components/autocomplete/operator.test.tsx b/x-pack/plugins/security_solution/public/common/components/autocomplete/operator.test.tsx deleted file mode 100644 index 5e00d2beb571..000000000000 --- a/x-pack/plugins/security_solution/public/common/components/autocomplete/operator.test.tsx +++ /dev/null @@ -1,225 +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 { mount } from 'enzyme'; -import { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui'; - -import { getField } from '../../../../../../../src/plugins/data/common/index_patterns/fields/fields.mocks'; -import { OperatorComponent } from './operator'; -import { isOperator, isNotOperator } from '@kbn/securitysolution-list-utils'; - -describe('OperatorComponent', () => { - test('it renders disabled if "isDisabled" is true', () => { - const wrapper = mount( - - ); - - expect( - wrapper.find(`[data-test-subj="operatorAutocompleteComboBox"] input`).prop('disabled') - ).toBeTruthy(); - }); - - test('it renders loading if "isLoading" is true', () => { - const wrapper = mount( - - ); - wrapper.find(`[data-test-subj="operatorAutocompleteComboBox"] button`).at(0).simulate('click'); - expect( - wrapper - .find(`EuiComboBoxOptionsList[data-test-subj="operatorAutocompleteComboBox-optionsList"]`) - .prop('isLoading') - ).toBeTruthy(); - }); - - test('it allows user to clear values if "isClearable" is true', () => { - const wrapper = mount( - - ); - - expect(wrapper.find(`button[data-test-subj="comboBoxClearButton"]`).exists()).toBeTruthy(); - }); - - test('it displays "operatorOptions" if param is passed in with items', () => { - const wrapper = mount( - - ); - - expect( - wrapper.find(`[data-test-subj="operatorAutocompleteComboBox"]`).at(0).prop('options') - ).toEqual([{ label: 'is not' }]); - }); - - test('it does not display "operatorOptions" if param is passed in with no items', () => { - const wrapper = mount( - - ); - - expect( - wrapper.find(`[data-test-subj="operatorAutocompleteComboBox"]`).at(0).prop('options') - ).toEqual([ - { - label: 'is', - }, - { - label: 'is not', - }, - { - label: 'is one of', - }, - { - label: 'is not one of', - }, - { - label: 'exists', - }, - { - label: 'does not exist', - }, - { - label: 'is in list', - }, - { - label: 'is not in list', - }, - ]); - }); - - test('it correctly displays selected operator', () => { - const wrapper = mount( - - ); - - expect( - wrapper.find(`[data-test-subj="operatorAutocompleteComboBox"] EuiComboBoxPill`).at(0).text() - ).toEqual('is'); - }); - - test('it only displays subset of operators if field type is nested', () => { - const wrapper = mount( - - ); - - expect( - wrapper.find(`[data-test-subj="operatorAutocompleteComboBox"]`).at(0).prop('options') - ).toEqual([{ label: 'is' }]); - }); - - test('it only displays subset of operators if field type is boolean', () => { - const wrapper = mount( - - ); - - expect( - wrapper.find(`[data-test-subj="operatorAutocompleteComboBox"]`).at(0).prop('options') - ).toEqual([ - { label: 'is' }, - { label: 'is not' }, - { label: 'exists' }, - { label: 'does not exist' }, - ]); - }); - - test('it invokes "onChange" when option selected', () => { - const mockOnChange = jest.fn(); - const wrapper = mount( - - ); - - ((wrapper.find(EuiComboBox).props() as unknown) as { - onChange: (a: EuiComboBoxOptionOption[]) => void; - }).onChange([{ label: 'is not' }]); - - expect(mockOnChange).toHaveBeenCalledWith([ - { message: 'is not', operator: 'excluded', type: 'match', value: 'is_not' }, - ]); - }); -}); diff --git a/x-pack/plugins/security_solution/public/common/components/autocomplete/operator.tsx b/x-pack/plugins/security_solution/public/common/components/autocomplete/operator.tsx deleted file mode 100644 index d8f49acd04b9..000000000000 --- a/x-pack/plugins/security_solution/public/common/components/autocomplete/operator.tsx +++ /dev/null @@ -1,82 +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, useMemo } from 'react'; -import { EuiComboBoxOptionOption, EuiComboBox } from '@elastic/eui'; - -import { IFieldType } from '../../../../../../../src/plugins/data/common'; -import { getOperators, getGenericComboBoxProps } from './helpers'; -import { GetGenericComboBoxPropsReturn, OperatorOption } from './types'; - -interface OperatorState { - placeholder: string; - selectedField: IFieldType | undefined; - operator: OperatorOption; - isLoading: boolean; - isDisabled: boolean; - isClearable: boolean; - operatorInputWidth?: number; - operatorOptions?: OperatorOption[]; - onChange: (arg: OperatorOption[]) => void; -} - -export const OperatorComponent: React.FC = ({ - placeholder, - selectedField, - operator, - isLoading = false, - isDisabled = false, - isClearable = false, - operatorOptions, - operatorInputWidth = 150, - onChange, -}): JSX.Element => { - const getLabel = useCallback(({ message }): string => message, []); - const optionsMemo = useMemo( - (): OperatorOption[] => - operatorOptions != null && operatorOptions.length > 0 - ? operatorOptions - : getOperators(selectedField), - [operatorOptions, selectedField] - ); - const selectedOptionsMemo = useMemo((): OperatorOption[] => (operator ? [operator] : []), [ - operator, - ]); - const { comboOptions, labels, selectedComboOptions } = useMemo( - (): GetGenericComboBoxPropsReturn => - getGenericComboBoxProps({ - options: optionsMemo, - selectedOptions: selectedOptionsMemo, - getLabel, - }), - [optionsMemo, selectedOptionsMemo, getLabel] - ); - - const handleValuesChange = (newOptions: EuiComboBoxOptionOption[]): void => { - const newValues: OperatorOption[] = newOptions.map( - ({ label }) => optionsMemo[labels.indexOf(label)] - ); - onChange(newValues); - }; - - return ( - - ); -}; - -OperatorComponent.displayName = 'Operator'; diff --git a/x-pack/plugins/security_solution/public/common/components/autocomplete/types.ts b/x-pack/plugins/security_solution/public/common/components/autocomplete/types.ts index 1d8e3e9aee28..07f1903fb70e 100644 --- a/x-pack/plugins/security_solution/public/common/components/autocomplete/types.ts +++ b/x-pack/plugins/security_solution/public/common/components/autocomplete/types.ts @@ -7,20 +7,8 @@ import { EuiComboBoxOptionOption } from '@elastic/eui'; -import type { - ListOperatorEnum as OperatorEnum, - ListOperatorTypeEnum as OperatorTypeEnum, -} from '@kbn/securitysolution-io-ts-list-types'; - export interface GetGenericComboBoxPropsReturn { comboOptions: EuiComboBoxOptionOption[]; labels: string[]; selectedComboOptions: EuiComboBoxOptionOption[]; } - -export interface OperatorOption { - message: string; - value: string; - operator: OperatorEnum; - type: OperatorTypeEnum; -}