From d6164aeecc3eab4df4bc91606099479776a2206c Mon Sep 17 00:00:00 2001 From: Ignacio Rivas Date: Fri, 4 Jun 2021 10:12:04 +0200 Subject: [PATCH] [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> --- .../__jest__/processors/processor.helpers.tsx | 4 + .../__jest__/processors/set.test.tsx | 146 ++++++++++++++++++ .../processor_form/processors/set.tsx | 61 +++++++- .../components/pipeline_editor/utils.test.ts | 18 ++- .../components/pipeline_editor/utils.ts | 17 ++ 5 files changed, 243 insertions(+), 3 deletions(-) create mode 100644 x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/set.test.tsx 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 9dd0d6cc72de..c00f09b2d2b0 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,6 +154,10 @@ type TestSubject = | 'separatorValueField.input' | 'quoteValueField.input' | 'emptyValueField.input' + | 'valueFieldInput' + | 'mediaTypeSelectorField' + | 'ignoreEmptyField.input' + | 'overrideField.input' | 'fieldsValueField.input' | 'saltValueField.input' | 'methodsValueField' diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/set.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/set.test.tsx new file mode 100644 index 000000000000..d7351c9dbf65 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/set.test.tsx @@ -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, + }); + }); +}); diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/set.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/set.tsx index 89ca373b9e65..fda34f8700b3 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/set.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/set.tsx @@ -10,7 +10,15 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; 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'; @@ -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: ( + + ), + }, /* Optional fields config */ override: { type: FIELD_TYPES.TOGGLE, @@ -83,6 +105,8 @@ const fieldsConfig: FieldsConfig = { * Disambiguate name from the Set data structure */ export const SetProcessor: FunctionComponent = () => { + const [{ fields }] = useFormData({ watch: 'fields.value' }); + return ( <> { path="fields.value" /> - + {hasTemplateSnippet(fields?.value) && ( + + )} + + ); diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/utils.test.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/utils.test.ts index 6f21285398e1..6e367a83bf8d 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/utils.test.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/utils.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { getValue, setValue } from './utils'; +import { getValue, setValue, hasTemplateSnippet } from './utils'; describe('get and set values', () => { 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); + }); +}); diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/utils.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/utils.ts index e07b9eba9062..1259dbd5a9b9 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/utils.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/utils.ts @@ -102,3 +102,20 @@ export const checkIfSamePath = (pathA: ProcessorSelector, pathB: ProcessorSelect if (pathA.length !== pathB.length) return false; 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); +};