[Ingest pipelines] add media_type to set processor (#101035)
* start working on conditionally showing the field * add tests and document regex matcher * add tests for set processor * fix broken tests * move path below componentProps * Add little comment about whitespaces handling * template snippets can also contain strings other Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
caa4bd111d
commit
d6164aeecc
|
@ -154,6 +154,10 @@ type TestSubject =
|
||||||
| 'separatorValueField.input'
|
| 'separatorValueField.input'
|
||||||
| 'quoteValueField.input'
|
| 'quoteValueField.input'
|
||||||
| 'emptyValueField.input'
|
| 'emptyValueField.input'
|
||||||
|
| 'valueFieldInput'
|
||||||
|
| 'mediaTypeSelectorField'
|
||||||
|
| 'ignoreEmptyField.input'
|
||||||
|
| 'overrideField.input'
|
||||||
| 'fieldsValueField.input'
|
| 'fieldsValueField.input'
|
||||||
| 'saltValueField.input'
|
| 'saltValueField.input'
|
||||||
| 'methodsValueField'
|
| 'methodsValueField'
|
||||||
|
|
|
@ -0,0 +1,146 @@
|
||||||
|
/*
|
||||||
|
* 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 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', () => {
|
||||||
|
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(SET_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 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, SET_TYPE);
|
||||||
|
expect(processors[0][SET_TYPE]).toEqual({
|
||||||
|
...defaultSetParameters,
|
||||||
|
field: 'field_1',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should allow to set mediaType when value is a template snippet', async () => {
|
||||||
|
const {
|
||||||
|
actions: { saveNewProcessor },
|
||||||
|
form,
|
||||||
|
exists,
|
||||||
|
} = testBed;
|
||||||
|
|
||||||
|
// Add "field" value (required)
|
||||||
|
form.setInputValue('fieldNameField.input', 'field_1');
|
||||||
|
|
||||||
|
// Shouldnt be able to set mediaType if value is not a template string
|
||||||
|
form.setInputValue('valueFieldInput', 'hello');
|
||||||
|
expect(exists('mediaTypeSelectorField')).toBe(false);
|
||||||
|
|
||||||
|
// Set value to a template snippet and media_type to a non-default value
|
||||||
|
form.setInputValue('valueFieldInput', '{{{hello}}}');
|
||||||
|
form.setSelectValue('mediaTypeSelectorField', 'text/plain');
|
||||||
|
|
||||||
|
// Save the field with new changes
|
||||||
|
await saveNewProcessor();
|
||||||
|
|
||||||
|
const processors = getProcessorValue(onUpdate, SET_TYPE);
|
||||||
|
expect(processors[0][SET_TYPE]).toEqual({
|
||||||
|
...defaultSetParameters,
|
||||||
|
field: 'field_1',
|
||||||
|
value: '{{{hello}}}',
|
||||||
|
media_type: 'text/plain',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('allows optional parameters to be set', async () => {
|
||||||
|
const {
|
||||||
|
actions: { saveNewProcessor },
|
||||||
|
form,
|
||||||
|
} = testBed;
|
||||||
|
|
||||||
|
// Add "field" value (required)
|
||||||
|
form.setInputValue('fieldNameField.input', 'field_1');
|
||||||
|
|
||||||
|
// Set optional parameteres
|
||||||
|
form.setInputValue('valueFieldInput', '{{{hello}}}');
|
||||||
|
form.toggleEuiSwitch('overrideField.input');
|
||||||
|
form.toggleEuiSwitch('ignoreEmptyField.input');
|
||||||
|
|
||||||
|
// Save the field with new changes
|
||||||
|
await saveNewProcessor();
|
||||||
|
|
||||||
|
const processors = getProcessorValue(onUpdate, SET_TYPE);
|
||||||
|
expect(processors[0][SET_TYPE]).toEqual({
|
||||||
|
...defaultSetParameters,
|
||||||
|
field: 'field_1',
|
||||||
|
value: '{{{hello}}}',
|
||||||
|
ignore_empty_value: true,
|
||||||
|
override: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -10,7 +10,15 @@ import { i18n } from '@kbn/i18n';
|
||||||
import { FormattedMessage } from '@kbn/i18n/react';
|
import { FormattedMessage } from '@kbn/i18n/react';
|
||||||
import { EuiCode } from '@elastic/eui';
|
import { EuiCode } from '@elastic/eui';
|
||||||
|
|
||||||
import { FIELD_TYPES, ToggleField, UseField, Field } from '../../../../../../shared_imports';
|
import {
|
||||||
|
FIELD_TYPES,
|
||||||
|
useFormData,
|
||||||
|
SelectField,
|
||||||
|
ToggleField,
|
||||||
|
UseField,
|
||||||
|
Field,
|
||||||
|
} from '../../../../../../shared_imports';
|
||||||
|
import { hasTemplateSnippet } from '../../../utils';
|
||||||
|
|
||||||
import { FieldsConfig, to, from } from './shared';
|
import { FieldsConfig, to, from } from './shared';
|
||||||
|
|
||||||
|
@ -35,6 +43,20 @@ const fieldsConfig: FieldsConfig = {
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
mediaType: {
|
||||||
|
type: FIELD_TYPES.SELECT,
|
||||||
|
defaultValue: 'application/json',
|
||||||
|
serializer: from.undefinedIfValue('application/json'),
|
||||||
|
label: i18n.translate('xpack.ingestPipelines.pipelineEditor.setForm.mediaTypeFieldLabel', {
|
||||||
|
defaultMessage: 'Media Type',
|
||||||
|
}),
|
||||||
|
helpText: (
|
||||||
|
<FormattedMessage
|
||||||
|
id="xpack.ingestPipelines.pipelineEditor.setForm.mediaTypeHelpText"
|
||||||
|
defaultMessage="Media type for encoding value."
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
/* Optional fields config */
|
/* Optional fields config */
|
||||||
override: {
|
override: {
|
||||||
type: FIELD_TYPES.TOGGLE,
|
type: FIELD_TYPES.TOGGLE,
|
||||||
|
@ -83,6 +105,8 @@ const fieldsConfig: FieldsConfig = {
|
||||||
* Disambiguate name from the Set data structure
|
* Disambiguate name from the Set data structure
|
||||||
*/
|
*/
|
||||||
export const SetProcessor: FunctionComponent = () => {
|
export const SetProcessor: FunctionComponent = () => {
|
||||||
|
const [{ fields }] = useFormData({ watch: 'fields.value' });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<FieldNameField
|
<FieldNameField
|
||||||
|
@ -102,12 +126,45 @@ export const SetProcessor: FunctionComponent = () => {
|
||||||
path="fields.value"
|
path="fields.value"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<UseField config={fieldsConfig.override} component={ToggleField} path="fields.override" />
|
{hasTemplateSnippet(fields?.value) && (
|
||||||
|
<UseField
|
||||||
|
componentProps={{
|
||||||
|
euiFieldProps: {
|
||||||
|
'data-test-subj': 'mediaTypeSelectorField',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
value: 'application/json',
|
||||||
|
text: 'application/json',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'text/plain',
|
||||||
|
text: 'text/plain',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'application/x-www-form-urlencoded',
|
||||||
|
text: 'application/x-www-form-urlencoded',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
config={fieldsConfig.mediaType}
|
||||||
|
component={SelectField}
|
||||||
|
path="fields.media_type"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<UseField
|
||||||
|
config={fieldsConfig.override}
|
||||||
|
component={ToggleField}
|
||||||
|
path="fields.override"
|
||||||
|
data-test-subj="overrideField"
|
||||||
|
/>
|
||||||
|
|
||||||
<UseField
|
<UseField
|
||||||
config={fieldsConfig.ignore_empty_value}
|
config={fieldsConfig.ignore_empty_value}
|
||||||
component={ToggleField}
|
component={ToggleField}
|
||||||
path="fields.ignore_empty_value"
|
path="fields.ignore_empty_value"
|
||||||
|
data-test-subj="ignoreEmptyField"
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { getValue, setValue } from './utils';
|
import { getValue, setValue, hasTemplateSnippet } from './utils';
|
||||||
|
|
||||||
describe('get and set values', () => {
|
describe('get and set values', () => {
|
||||||
const testObject = Object.freeze([{ onFailure: [{ onFailure: 1 }] }]);
|
const testObject = Object.freeze([{ onFailure: [{ onFailure: 1 }] }]);
|
||||||
|
@ -35,3 +35,19 @@ describe('get and set values', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('template snippets', () => {
|
||||||
|
it('knows when a string contains an invalid template snippet', () => {
|
||||||
|
expect(hasTemplateSnippet('')).toBe(false);
|
||||||
|
expect(hasTemplateSnippet('{}')).toBe(false);
|
||||||
|
expect(hasTemplateSnippet('{{{}}}')).toBe(false);
|
||||||
|
expect(hasTemplateSnippet('{{hello}}')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('knows when a string contains a valid template snippet', () => {
|
||||||
|
expect(hasTemplateSnippet('{{{hello}}}')).toBe(true);
|
||||||
|
expect(hasTemplateSnippet('hello{{{world}}}')).toBe(true);
|
||||||
|
expect(hasTemplateSnippet('{{{hello}}}world')).toBe(true);
|
||||||
|
expect(hasTemplateSnippet('{{{hello.world}}}')).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -102,3 +102,20 @@ export const checkIfSamePath = (pathA: ProcessorSelector, pathB: ProcessorSelect
|
||||||
if (pathA.length !== pathB.length) return false;
|
if (pathA.length !== pathB.length) return false;
|
||||||
return pathA.join('.') === pathB.join('.');
|
return pathA.join('.') === pathB.join('.');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Given a string it checks if it contains a valid mustache template snippet.
|
||||||
|
*
|
||||||
|
* Note: This allows strings with spaces such as: {{{hello world}}}. I figured we
|
||||||
|
* should use .+ instead of \S (disallow all whitespaces) because the backend seems
|
||||||
|
* to allow spaces inside the template snippet anyway.
|
||||||
|
*
|
||||||
|
* See: https://www.elastic.co/guide/en/elasticsearch/reference/master/ingest.html#template-snippets
|
||||||
|
*/
|
||||||
|
export const hasTemplateSnippet = (str: string = '') => {
|
||||||
|
// Matches when:
|
||||||
|
// * contains a {{{
|
||||||
|
// * Followed by all strings of length >= 1
|
||||||
|
// * And followed by }}}
|
||||||
|
return /{{{.+}}}/.test(str);
|
||||||
|
};
|
||||||
|
|
Loading…
Reference in a new issue