diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/processor.helpers.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/processor.helpers.tsx index 15e8c323b1308e..5f6ace20694104 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/processor.helpers.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/processor.helpers.tsx @@ -156,6 +156,9 @@ type TestSubject = | 'separatorValueField.input' | 'quoteValueField.input' | 'emptyValueField.input' + | 'extractDeviceTypeSwitch.input' + | 'propertiesValueField' + | 'regexFileField.input' | 'valueFieldInput' | 'mediaTypeSelectorField' | 'ignoreEmptyField.input' diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/user_agent.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/user_agent.test.tsx new file mode 100644 index 00000000000000..fa1c24c9dfb392 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/user_agent.test.tsx @@ -0,0 +1,125 @@ +/* + * 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 { act } from 'react-dom/test-utils'; +import { setup, SetupResult, getProcessorValue } from './processor.helpers'; + +// Default parameter values automatically added to the user agent processor when saved +const defaultUserAgentParameters = { + if: undefined, + regex_file: undefined, + properties: undefined, + description: undefined, + ignore_missing: undefined, + ignore_failure: undefined, + extract_device_type: undefined, +}; + +const USER_AGENT_TYPE = 'user_agent'; + +describe('Processor: User Agent', () => { + let onUpdate: jest.Mock; + let testBed: SetupResult; + + beforeAll(() => { + jest.useFakeTimers(); + }); + + afterAll(() => { + jest.useRealTimers(); + }); + + beforeEach(async () => { + onUpdate = jest.fn(); + + await act(async () => { + testBed = await setup({ + value: { + processors: [], + }, + onFlyoutOpen: jest.fn(), + onUpdate, + }); + }); + + testBed.component.update(); + + // Open flyout to add new processor + testBed.actions.addProcessor(); + // Add type (the other fields are not visible until a type is selected) + await testBed.actions.addProcessorType(USER_AGENT_TYPE); + }); + + test('prevents form submission if required fields are not provided', async () => { + const { + actions: { saveNewProcessor }, + form, + } = testBed; + + // Click submit button with only the processor type defined + await saveNewProcessor(); + + // Expect form error as "field" is required parameter + expect(form.getErrorsMessages()).toEqual(['A field value is required.']); + }); + + test('saves with just the default parameter value', async () => { + const { + actions: { saveNewProcessor }, + form, + } = testBed; + + // Add "field" value (required) + form.setInputValue('fieldNameField.input', 'field_1'); + // Save the field + await saveNewProcessor(); + + const processors = getProcessorValue(onUpdate, USER_AGENT_TYPE); + expect(processors[0][USER_AGENT_TYPE]).toEqual({ + ...defaultUserAgentParameters, + field: 'field_1', + }); + }); + + test('allows optional parameters to be set', async () => { + const { + actions: { saveNewProcessor }, + form, + find, + component, + } = testBed; + + // Add "field" value (required) + form.setInputValue('fieldNameField.input', 'field_1'); + + // Set optional parameteres + form.setInputValue('targetField.input', 'target_field'); + form.setInputValue('regexFileField.input', 'hello*'); + form.toggleEuiSwitch('ignoreMissingSwitch.input'); + form.toggleEuiSwitch('ignoreFailureSwitch.input'); + form.toggleEuiSwitch('extractDeviceTypeSwitch.input'); + await act(async () => { + find('propertiesValueField').simulate('change', [{ label: 'os' }]); + }); + component.update(); + + // Save the field with new changes + await saveNewProcessor(); + + const processors = getProcessorValue(onUpdate, USER_AGENT_TYPE); + expect(processors[0][USER_AGENT_TYPE]).toEqual({ + ...defaultUserAgentParameters, + field: 'field_1', + target_field: 'target_field', + properties: ['os'], + regex_file: 'hello*', + extract_device_type: true, + ignore_missing: true, + ignore_failure: true, + }); + }); +}); diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/common_fields/properties_field.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/common_fields/properties_field.tsx index dd52375a19436d..c8a50cf64484e4 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/common_fields/properties_field.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/common_fields/properties_field.tsx @@ -6,9 +6,9 @@ */ import React, { FunctionComponent } from 'react'; +import { EuiComboBoxProps } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { EuiComboBoxOptionOption } from '@elastic/eui'; import { ComboBoxField, FIELD_TYPES, UseField } from '../../../../../../../shared_imports'; import { FieldsConfig, to } from '../shared'; @@ -29,10 +29,10 @@ const fieldsConfig: FieldsConfig = { interface Props { helpText?: React.ReactNode; - propertyOptions?: EuiComboBoxOptionOption[]; + euiFieldProps?: EuiComboBoxProps; } -export const PropertiesField: FunctionComponent = ({ helpText, propertyOptions }) => { +export const PropertiesField: FunctionComponent = ({ helpText, euiFieldProps }) => { return ( = ({ helpText, propertyOp }} component={ComboBoxField} path="fields.properties" - componentProps={{ - euiFieldProps: { - options: propertyOptions || [], - noSuggestions: !propertyOptions, - }, - }} + componentProps={{ euiFieldProps }} /> ); }; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/user_agent.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/user_agent.tsx index 893e52bcc0073e..2b5a68f799b7e5 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/user_agent.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/user_agent.tsx @@ -6,20 +6,20 @@ */ import React, { FunctionComponent } from 'react'; -import { EuiCode } from '@elastic/eui'; +import { EuiCode, EuiBetaBadge, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import { EuiComboBoxOptionOption } from '@elastic/eui'; -import { FIELD_TYPES, UseField, Field } from '../../../../../../shared_imports'; +import { FIELD_TYPES, ToggleField, UseField, Field } from '../../../../../../shared_imports'; -import { FieldsConfig, from } from './shared'; +import { FieldsConfig, from, to } from './shared'; import { IgnoreMissingField } from './common_fields/ignore_missing_field'; import { FieldNameField } from './common_fields/field_name_field'; import { TargetField } from './common_fields/target_field'; import { PropertiesField } from './common_fields/properties_field'; -const propertyOptions: EuiComboBoxOptionOption[] = [ +const propertyOptions: Array> = [ { label: 'name' }, { label: 'os' }, { label: 'device' }, @@ -47,6 +47,18 @@ const fieldsConfig: FieldsConfig = { } ), }, + extract_device_type: { + type: FIELD_TYPES.TOGGLE, + defaultValue: false, + deserializer: to.booleanOrUndef, + serializer: from.undefinedIfValue(false), + helpText: i18n.translate( + 'xpack.ingestPipelines.pipelineEditor.userAgentForm.extractDeviceTypeFieldHelpText', + { + defaultMessage: 'Extracts device type from the user agent string.', + } + ), + }, }; export const UserAgent: FunctionComponent = () => { @@ -59,7 +71,12 @@ export const UserAgent: FunctionComponent = () => { )} /> - + { 'xpack.ingestPipelines.pipelineEditor.userAgentForm.propertiesFieldHelpText', { defaultMessage: 'Properties added to the target field.' } )} - propertyOptions={propertyOptions} + euiFieldProps={{ + options: propertyOptions, + noSuggestions: false, + 'data-test-subj': 'propertiesValueField', + }} + /> + + + + + + + + + + ), + }} />