diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/fingerprint.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/fingerprint.test.tsx new file mode 100644 index 00000000000000..7c2ca012a04607 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/fingerprint.test.tsx @@ -0,0 +1,128 @@ +/* + * 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 registered domain processor when saved +const defaultFingerprintParameters = { + if: undefined, + tag: undefined, + method: undefined, + salt: undefined, + description: undefined, + ignore_missing: undefined, + ignore_failure: undefined, + target_field: undefined, +}; + +const FINGERPRINT_TYPE = 'fingerprint'; + +describe('Processor: Fingerprint', () => { + 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(FINGERPRINT_TYPE); + }); + + test('prevents form submission if required fields are not provided', async () => { + const { + actions: { saveNewProcessor }, + form, + } = testBed; + + // Click submit button with only the type defined + await saveNewProcessor(); + + // Expect form error as "field" is required parameter + expect(form.getErrorsMessages()).toEqual(['A field value is required.']); + }); + + test('saves with default parameter values', async () => { + const { + actions: { saveNewProcessor }, + find, + component, + } = testBed; + + // Add "fields" value (required) + await act(async () => { + find('fieldsValueField.input').simulate('change', [{ label: 'user' }]); + }); + component.update(); + // Save the field + await saveNewProcessor(); + + const processors = getProcessorValue(onUpdate, FINGERPRINT_TYPE); + expect(processors[0][FINGERPRINT_TYPE]).toEqual({ + ...defaultFingerprintParameters, + fields: ['user'], + }); + }); + + test('allows optional parameters to be set', async () => { + const { + actions: { saveNewProcessor }, + form, + find, + component, + } = testBed; + + // Add "fields" value (required) + await act(async () => { + find('fieldsValueField.input').simulate('change', [{ label: 'user' }]); + }); + component.update(); + + // Set optional parameteres + form.setInputValue('targetField.input', 'target_field'); + form.setSelectValue('methodsValueField', 'SHA-256'); + form.setInputValue('saltValueField.input', 'salt'); + form.toggleEuiSwitch('ignoreMissingSwitch.input'); + form.toggleEuiSwitch('ignoreFailureSwitch.input'); + + // Save the field with new changes + await saveNewProcessor(); + + const processors = getProcessorValue(onUpdate, FINGERPRINT_TYPE); + expect(processors[0][FINGERPRINT_TYPE]).toEqual({ + ...defaultFingerprintParameters, + fields: ['user'], + target_field: 'target_field', + method: 'SHA-256', + salt: 'salt', + ignore_missing: true, + ignore_failure: true, + }); + }); +}); 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 d69ceb385ddd71..9dd0d6cc72de17 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 @@ -154,4 +154,7 @@ type TestSubject = | 'separatorValueField.input' | 'quoteValueField.input' | 'emptyValueField.input' + | 'fieldsValueField.input' + | 'saltValueField.input' + | 'methodsValueField' | 'trimSwitch.input'; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/fingerprint.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/fingerprint.tsx new file mode 100644 index 00000000000000..5e52d560020c01 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/fingerprint.tsx @@ -0,0 +1,152 @@ +/* + * 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, { FunctionComponent } from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiCode } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import { FieldsConfig, from, to } from './shared'; +import { TargetField } from './common_fields/target_field'; +import { IgnoreMissingField } from './common_fields/ignore_missing_field'; +import { + FIELD_TYPES, + Field, + UseField, + SelectField, + ComboBoxField, + fieldValidators, +} from '../../../../../../shared_imports'; + +const fieldsConfig: FieldsConfig = { + fields: { + type: FIELD_TYPES.COMBO_BOX, + deserializer: to.arrayOfStrings, + label: i18n.translate('xpack.ingestPipelines.pipelineEditor.fingerprint.fieldNameField', { + defaultMessage: 'Fields', + }), + helpText: i18n.translate('xpack.ingestPipelines.pipelineEditor.fingerprint.fieldNameHelpText', { + defaultMessage: 'Fields to include in the fingerprint.', + }), + validations: [ + { + validator: fieldValidators.emptyField( + i18n.translate( + 'xpack.ingestPipelines.pipelineEditor.fingerprint.fieldNameRequiredError', + { + defaultMessage: 'A field value is required.', + } + ) + ), + }, + ], + }, + salt: { + type: FIELD_TYPES.TEXT, + serializer: from.emptyStringToUndefined, + label: i18n.translate('xpack.ingestPipelines.pipelineEditor.fingerprint.saltFieldLabel', { + defaultMessage: 'Salt (optional)', + }), + helpText: ( + + ), + }, + method: { + type: FIELD_TYPES.SELECT, + defaultValue: 'SHA-1', + serializer: (v) => (v === 'SHA-1' || v === '' ? undefined : v), + label: i18n.translate('xpack.ingestPipelines.pipelineEditor.fingerprint.methodFieldLabel', { + defaultMessage: 'Method', + }), + helpText: ( + + ), + }, +}; + +export const Fingerprint: FunctionComponent = () => { + return ( + <> + + + {'fingerprint'}, + }} + /> + } + /> + + + + + + {'fields'}, + }} + /> + } + /> + + ); +}; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/index.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/index.ts index 4fb4365c477b56..5e3e5f82478bd6 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/index.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/index.ts @@ -17,6 +17,7 @@ export { DotExpander } from './dot_expander'; export { Drop } from './drop'; export { Enrich } from './enrich'; export { Fail } from './fail'; +export { Fingerprint } from './fingerprint'; export { Foreach } from './foreach'; export { GeoIP } from './geoip'; export { Grok } from './grok'; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/shared/map_processor_type_to_form.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/shared/map_processor_type_to_form.tsx index b5e42ea56bdf8b..983fb0ea67bb0f 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/shared/map_processor_type_to_form.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/shared/map_processor_type_to_form.tsx @@ -23,6 +23,7 @@ import { Drop, Enrich, Fail, + Fingerprint, Foreach, GeoIP, Grok, @@ -308,6 +309,20 @@ export const mapProcessorTypeToDescriptor: MapProcessorTypeToDescriptor = { defaultMessage: 'Raises an exception that halts execution', }), }, + fingerprint: { + FieldsComponent: Fingerprint, + docLinkPath: '/fingerprint-processor.html', + label: i18n.translate('xpack.ingestPipelines.processors.label.fingerprint', { + defaultMessage: 'Fingerprint', + }), + typeDescription: i18n.translate('xpack.ingestPipelines.processors.description.fingerprint', { + defaultMessage: 'Computes a hash of the document’s content.', + }), + getDefaultDescription: () => + i18n.translate('xpack.ingestPipelines.processors.defaultDescription.fingerprint', { + defaultMessage: 'Computes a hash of the document’s content.', + }), + }, foreach: { FieldsComponent: Foreach, docLinkPath: '/foreach-processor.html',