Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Ingest pipelines] Add copy_from to set processor #104070

Merged
merged 17 commits into from Jul 27, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -172,8 +172,6 @@ type TestSubject =
| 'valueFieldInput'
| 'mediaTypeSelectorField'
| 'networkDirectionField.input'
| 'sourceIpField.input'
| 'destinationIpField.input'
| 'toggleCustomField'
| 'ignoreEmptyField.input'
| 'overrideField.input'
Expand All @@ -189,4 +187,5 @@ type TestSubject =
| 'ianaField.input'
| 'transportField.input'
| 'seedField.input'
| 'copyFromInput'
| 'trimSwitch.input';
Expand Up @@ -8,19 +8,6 @@
import { act } from 'react-dom/test-utils';
import { setup, SetupResult, getProcessorValue } from './processor.helpers';

// Default parameter values automatically added to the set processor when saved
const defaultSetParameters = {
value: '',
if: undefined,
tag: undefined,
override: undefined,
media_type: undefined,
description: undefined,
ignore_missing: undefined,
ignore_failure: undefined,
ignore_empty_value: undefined,
};

const SET_TYPE = 'set';

describe('Processor: Set', () => {
Expand Down Expand Up @@ -66,7 +53,10 @@ describe('Processor: Set', () => {
await saveNewProcessor();

// Expect form error as "field" is required parameter
expect(form.getErrorsMessages()).toEqual(['A field value is required.']);
expect(form.getErrorsMessages()).toEqual([
'A field value is required.',
'A value is required.',
]);
});

test('saves with default parameter value', async () => {
Expand All @@ -75,15 +65,43 @@ describe('Processor: Set', () => {
form,
} = testBed;

// Add "field" value (required)
// Add required fields
form.setInputValue('valueFieldInput', 'value');
form.setInputValue('fieldNameField.input', 'field_1');
// Save the field
await saveNewProcessor();

const processors = getProcessorValue(onUpdate, SET_TYPE);
expect(processors[0][SET_TYPE]).toEqual({
...defaultSetParameters,
field: 'field_1',
value: 'value',
});
});

test('allows to save the the copy_from value', async () => {
const {
actions: { saveNewProcessor },
form,
find,
} = testBed;

// Add required fields
form.setInputValue('fieldNameField.input', 'field_1');

// Set value field
form.setInputValue('valueFieldInput', 'value');

// Toggle to copy_from field and set a random value
find('toggleCustomField').simulate('click');
form.setInputValue('copyFromInput', 'copy_from');

// Save the field with new changes
await saveNewProcessor();

const processors = getProcessorValue(onUpdate, SET_TYPE);
expect(processors[0][SET_TYPE]).toEqual({
field: 'field_1',
copy_from: 'copy_from',
});
});

Expand All @@ -110,7 +128,6 @@ describe('Processor: Set', () => {

const processors = getProcessorValue(onUpdate, SET_TYPE);
expect(processors[0][SET_TYPE]).toEqual({
...defaultSetParameters,
field: 'field_1',
value: '{{{hello}}}',
media_type: 'text/plain',
Expand All @@ -136,7 +153,6 @@ describe('Processor: Set', () => {

const processors = getProcessorValue(onUpdate, SET_TYPE);
expect(processors[0][SET_TYPE]).toEqual({
...defaultSetParameters,
field: 'field_1',
value: '{{{hello}}}',
ignore_empty_value: true,
Expand Down
Expand Up @@ -9,7 +9,7 @@ import React, { FunctionComponent, useState, useCallback, useMemo } from 'react'
import { i18n } from '@kbn/i18n';
import { isEmpty } from 'lodash';
import { FormattedMessage } from '@kbn/i18n/react';
import { EuiButtonEmpty, EuiCode } from '@elastic/eui';
import { EuiCode, EuiLink, EuiText } from '@elastic/eui';

import {
FIELD_TYPES,
Expand Down Expand Up @@ -131,11 +131,14 @@ const getInternalNetworkConfig: (
),
},
labelAppend: (
<EuiButtonEmpty size="xs" onClick={toggleCustom} data-test-subj="toggleCustomField">
{i18n.translate('xpack.ingestPipelines.pipelineEditor.internalNetworkCustomLabel', {
defaultMessage: 'Use custom field',
})}
</EuiButtonEmpty>
<EuiText size="xs">
<EuiLink onClick={toggleCustom} data-test-subj="toggleCustomField">
<FormattedMessage
id="xpack.ingestPipelines.pipelineEditor.internalNetworkCustomLabel"
defaultMessage="Use custom field"
/>
</EuiLink>
</EuiText>
),
key: 'preset',
},
Expand Down Expand Up @@ -171,11 +174,14 @@ const getInternalNetworkConfig: (
),
},
labelAppend: (
<EuiButtonEmpty size="xs" onClick={toggleCustom} data-test-subj="toggleCustomField">
{i18n.translate('xpack.ingestPipelines.pipelineEditor.internalNetworkPredefinedLabel', {
defaultMessage: 'Use preset field',
})}
</EuiButtonEmpty>
<EuiText size="xs">
<EuiLink onClick={toggleCustom} data-test-subj="toggleCustomField">
<FormattedMessage
id="xpack.ingestPipelines.pipelineEditor.internalNetworkPredefinedLabel"
defaultMessage="Use preset field"
/>
</EuiLink>
</EuiText>
),
key: 'custom',
},
Expand Down
Expand Up @@ -5,10 +5,11 @@
* 2.0.
*/

import React, { FunctionComponent } from 'react';
import React, { FunctionComponent, useState, useCallback, useMemo } from 'react';
import { i18n } from '@kbn/i18n';
import { isEmpty } from 'lodash';
sabarasaba marked this conversation as resolved.
Show resolved Hide resolved
import { EuiCode, EuiLink, EuiText } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { EuiCode } from '@elastic/eui';

import {
FIELD_TYPES,
Expand All @@ -17,32 +18,27 @@ import {
ToggleField,
UseField,
Field,
FieldHook,
FieldConfig,
useFormContext,
} from '../../../../../../shared_imports';
import { hasTemplateSnippet } from '../../../utils';

import { FieldsConfig, to, from } from './shared';

import { FieldNameField } from './common_fields/field_name_field';

interface ValueToggleTypes {
value: string;
copy_from: string;
}

type ValueToggleFields = {
[K in keyof ValueToggleTypes]: FieldHook<ValueToggleTypes[K]>;
};

// Optional fields config
const fieldsConfig: FieldsConfig = {
/* Required fields config */
// This is a required field, but we exclude validation because we accept empty values as ''
value: {
type: FIELD_TYPES.TEXT,
deserializer: String,
label: i18n.translate('xpack.ingestPipelines.pipelineEditor.setForm.valueFieldLabel', {
defaultMessage: 'Value',
}),
helpText: (
<FormattedMessage
id="xpack.ingestPipelines.pipelineEditor.setForm.valueFieldHelpText"
defaultMessage="Value for the field. A blank value will set {emptyString}."
values={{
emptyString: <EuiCode>{'""'}</EuiCode>,
}}
/>
),
},
mediaType: {
type: FIELD_TYPES.SELECT,
defaultValue: 'application/json',
Expand All @@ -57,7 +53,6 @@ const fieldsConfig: FieldsConfig = {
/>
),
},
/* Optional fields config */
sabarasaba marked this conversation as resolved.
Show resolved Hide resolved
override: {
type: FIELD_TYPES.TOGGLE,
defaultValue: true,
Expand Down Expand Up @@ -101,11 +96,134 @@ const fieldsConfig: FieldsConfig = {
},
};

// Required fields config
const getValueConfig: (
toggleCustom: () => void
) => Record<
keyof ValueToggleFields,
{
path: string;
config?: FieldConfig<any>;
euiFieldProps?: Record<string, any>;
labelAppend: JSX.Element;
}
> = (toggleCustom: () => void) => ({
value: {
path: 'fields.value',
euiFieldProps: {
'data-test-subj': 'valueFieldInput',
},
config: {
type: FIELD_TYPES.TEXT,
serializer: from.emptyStringToUndefined,
label: i18n.translate('xpack.ingestPipelines.pipelineEditor.setForm.valueFieldLabel', {
defaultMessage: 'Value',
}),
helpText: (
<FormattedMessage
id="xpack.ingestPipelines.pipelineEditor.setForm.valueFieldHelpText"
defaultMessage="Value for the field."
/>
),
fieldsToValidateOnChange: ['fields.value', 'fields.copy_from'],
validations: [
{
validator: ({ value, path, formData }) => {
if (isEmpty(value) && isEmpty(formData['fields.copy_from'])) {
return {
path,
message: i18n.translate('xpack.ingestPipelines.pipelineEditor.requiredValue', {
defaultMessage: 'A value is required.',
}),
};
}
},
},
],
},
labelAppend: (
<EuiText size="xs">
<EuiLink onClick={toggleCustom} data-test-subj="toggleCustomField">
<FormattedMessage
id="xpack.ingestPipelines.pipelineEditor.useCopyFromLabel"
defaultMessage="Use copy from field"
/>
</EuiLink>
</EuiText>
),
key: 'value',
},
copy_from: {
path: 'fields.copy_from',
euiFieldProps: {
'data-test-subj': 'copyFromInput',
},
config: {
type: FIELD_TYPES.TEXT,
serializer: from.emptyStringToUndefined,
fieldsToValidateOnChange: ['fields.value', 'fields.copy_from'],
validations: [
{
validator: ({ value, path, formData }) => {
if (isEmpty(value) && isEmpty(formData['fields.value'])) {
return {
path,
message: i18n.translate('xpack.ingestPipelines.pipelineEditor.requiredCopyFrom', {
defaultMessage: 'A copy from value is required.',
}),
};
}
},
},
],
label: i18n.translate('xpack.ingestPipelines.pipelineEditor.setForm.copyFromFieldLabel', {
defaultMessage: 'Copy from',
}),
helpText: (
<FormattedMessage
id="xpack.ingestPipelines.pipelineEditor.setForm.copyFromFieldHelpText"
defaultMessage="Field to copy into {field}."
values={{
field: <EuiCode>{'Field'}</EuiCode>,
}}
/>
),
},
labelAppend: (
<EuiText size="xs">
<EuiLink onClick={toggleCustom} data-test-subj="toggleCustomField">
<FormattedMessage
id="xpack.ingestPipelines.pipelineEditor.useValueLabel"
defaultMessage="Use value field"
/>
</EuiLink>
</EuiText>
),
key: 'copy_from',
},
});

/**
* Disambiguate name from the Set data structure
*/
export const SetProcessor: FunctionComponent = () => {
const [{ fields }] = useFormData({ watch: 'fields.value' });
const { getFieldDefaultValue } = useFormContext();
const [{ fields }] = useFormData({ watch: ['fields.value', 'fields.copy_from'] });

const isCopyFromDefined = getFieldDefaultValue('fields.copy_from') !== undefined;
const [isCopyFromEnabled, setIsCopyFrom] = useState<boolean>(isCopyFromDefined);

const toggleCustom = useCallback(() => {
setIsCopyFrom((prev) => !prev);
}, []);

const valueFieldProps = useMemo(
() =>
isCopyFromEnabled
? getValueConfig(toggleCustom).copy_from
: getValueConfig(toggleCustom).value,
[isCopyFromEnabled, toggleCustom]
);

return (
<>
Expand All @@ -115,16 +233,7 @@ export const SetProcessor: FunctionComponent = () => {
})}
/>

<UseField
config={fieldsConfig.value}
component={Field}
componentProps={{
euiFieldProps: {
'data-test-subj': 'valueFieldInput',
},
}}
path="fields.value"
/>
<UseField {...valueFieldProps} component={Field} />

{hasTemplateSnippet(fields?.value) && (
<UseField
Expand Down