Skip to content

Commit

Permalink
[Ingest pipelines] add support for fingerprint processor (elastic#100541
Browse files Browse the repository at this point in the history
  • Loading branch information
sabarasaba committed May 26, 2021
1 parent 7b97777 commit 20dca6d
Show file tree
Hide file tree
Showing 5 changed files with 299 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -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,
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -154,4 +154,7 @@ type TestSubject =
| 'separatorValueField.input'
| 'quoteValueField.input'
| 'emptyValueField.input'
| 'fieldsValueField.input'
| 'saltValueField.input'
| 'methodsValueField'
| 'trimSwitch.input';
Original file line number Diff line number Diff line change
@@ -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: (
<FormattedMessage
id="xpack.ingestPipelines.pipelineEditor.fingerprint.saltHelpText"
defaultMessage="Salt value for the hash function."
/>
),
},
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: (
<FormattedMessage
id="xpack.ingestPipelines.pipelineEditor.fingerprint.methodHelpText"
defaultMessage="Hash method used to compute the fingerprint."
/>
),
},
};

export const Fingerprint: FunctionComponent = () => {
return (
<>
<UseField
config={fieldsConfig.fields}
component={ComboBoxField}
path="fields.fields"
data-test-subj="fieldsValueField"
/>

<TargetField
helpText={
<FormattedMessage
id="xpack.ingestPipelines.pipelineEditor.fingerprint.targetFieldHelpText"
defaultMessage="Output field. Defaults to {field}."
values={{
field: <EuiCode>{'fingerprint'}</EuiCode>,
}}
/>
}
/>

<UseField
componentProps={{
euiFieldProps: {
'data-test-subj': 'methodsValueField',
options: [
{
value: 'MD5',
text: 'MD5',
},
{
value: 'SHA-1',
text: 'SHA-1',
},
{
value: 'SHA-256',
text: 'SHA-256',
},
{
value: 'SHA-512',
text: 'SHA-512',
},
{
value: 'MurmurHash3',
text: 'MurmurHash3',
},
],
},
}}
config={fieldsConfig.method}
component={SelectField}
path="fields.method"
/>

<UseField
config={fieldsConfig.salt}
component={Field}
path="fields.salt"
data-test-subj="saltValueField"
/>

<IgnoreMissingField
helpText={
<FormattedMessage
id="xpack.ingestPipelines.pipelineEditor.fingerprint.ignoreMissingFieldHelpText"
defaultMessage="Ignore any missing {field}."
values={{
field: <EuiCode>{'fields'}</EuiCode>,
}}
/>
}
/>
</>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
Drop,
Enrich,
Fail,
Fingerprint,
Foreach,
GeoIP,
Grok,
Expand Down Expand Up @@ -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',
Expand Down

0 comments on commit 20dca6d

Please sign in to comment.