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 183777ca765b4..9101e64278dc6 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 @@ -172,8 +172,6 @@ type TestSubject = | 'valueFieldInput' | 'mediaTypeSelectorField' | 'networkDirectionField.input' - | 'sourceIpField.input' - | 'destinationIpField.input' | 'toggleCustomField' | 'ignoreEmptyField.input' | 'overrideField.input' @@ -189,4 +187,5 @@ type TestSubject = | 'ianaField.input' | 'transportField.input' | 'seedField.input' + | 'copyFromInput' | 'trimSwitch.input'; 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 index d7351c9dbf65f..544b8aeb51c05 100644 --- 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 @@ -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', () => { @@ -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 () => { @@ -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', }); }); @@ -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', @@ -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, diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/network_direction.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/network_direction.tsx index 2026a77bc6566..22f2226d80b10 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/network_direction.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/network_direction.tsx @@ -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, @@ -131,11 +131,14 @@ const getInternalNetworkConfig: ( ), }, labelAppend: ( - - {i18n.translate('xpack.ingestPipelines.pipelineEditor.internalNetworkCustomLabel', { - defaultMessage: 'Use custom field', - })} - + + + + + ), key: 'preset', }, @@ -171,11 +174,14 @@ const getInternalNetworkConfig: ( ), }, labelAppend: ( - - {i18n.translate('xpack.ingestPipelines.pipelineEditor.internalNetworkPredefinedLabel', { - defaultMessage: 'Use preset field', - })} - + + + + + ), key: 'custom', }, 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 fda34f8700b33..16d89fcbfb119 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 @@ -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'; +import { EuiCode, EuiLink, EuiText } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiCode } from '@elastic/eui'; import { FIELD_TYPES, @@ -17,6 +18,9 @@ import { ToggleField, UseField, Field, + FieldHook, + FieldConfig, + useFormContext, } from '../../../../../../shared_imports'; import { hasTemplateSnippet } from '../../../utils'; @@ -24,25 +28,17 @@ 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; +}; + +// 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: ( - {'""'}, - }} - /> - ), - }, mediaType: { type: FIELD_TYPES.SELECT, defaultValue: 'application/json', @@ -57,7 +53,6 @@ const fieldsConfig: FieldsConfig = { /> ), }, - /* Optional fields config */ override: { type: FIELD_TYPES.TOGGLE, defaultValue: true, @@ -101,11 +96,134 @@ const fieldsConfig: FieldsConfig = { }, }; +// Required fields config +const getValueConfig: ( + toggleCustom: () => void +) => Record< + keyof ValueToggleFields, + { + path: string; + config?: FieldConfig; + euiFieldProps?: Record; + 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: ( + + ), + 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: ( + + + + + + ), + 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: ( + {'Field'}, + }} + /> + ), + }, + labelAppend: ( + + + + + + ), + 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(isCopyFromDefined); + + const toggleCustom = useCallback(() => { + setIsCopyFrom((prev) => !prev); + }, []); + + const valueFieldProps = useMemo( + () => + isCopyFromEnabled + ? getValueConfig(toggleCustom).copy_from + : getValueConfig(toggleCustom).value, + [isCopyFromEnabled, toggleCustom] + ); return ( <> @@ -115,16 +233,7 @@ export const SetProcessor: FunctionComponent = () => { })} /> - + {hasTemplateSnippet(fields?.value) && ( - i18n.translate('xpack.ingestPipelines.processors.defaultDescription.set', { + getDefaultDescription: ({ field, value, copy_from: copyFrom }) => { + if (copyFrom) { + return i18n.translate('xpack.ingestPipelines.processors.defaultDescription.setCopyFrom', { + defaultMessage: 'Sets value of "{field}" to the value of "{copyFrom}"', + values: { + field, + copyFrom, + }, + }); + } + + return i18n.translate('xpack.ingestPipelines.processors.defaultDescription.set', { defaultMessage: 'Sets value of "{field}" to "{value}"', values: { field, value, }, - }), + }); + }, }, set_security_user: { FieldsComponent: SetSecurityUser, diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/context/processors_context.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/context/processors_context.tsx index 6233b220ae773..f6848a7f2bf45 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/context/processors_context.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/context/processors_context.tsx @@ -156,6 +156,8 @@ export const PipelineProcessorsContextProvider: FunctionComponent = ({ // We manually add fields that we **don't** want to be treated as "unknownOptions" 'internal_networks', 'internal_networks_field', + 'value', + 'copy_from', ]; // If the processor type is changed while editing, we need to ignore unkownOptions as they diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 362b5c416bf19..4044d5c10f86d 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -6702,10 +6702,6 @@ "xpack.canvas.functions.if.args.elseHelpText": "条件が {BOOLEAN_FALSE} の場合の戻り値です。指定されておらず、条件が満たされていない場合は、元の {CONTEXT} が戻されます。", "xpack.canvas.functions.if.args.thenHelpText": "条件が {BOOLEAN_TRUE} の場合の戻り値です。指定されておらず、条件が満たされている場合は、元の {CONTEXT} が戻されます。", "xpack.canvas.functions.ifHelpText": "条件付きロジックを実行します。", - "expressionImage.functions.image.args.dataurlHelpText": "画像の {https} {URL} または {BASE64} データ {URL} です。", - "expressionImage.functions.image.args.modeHelpText": "{contain} はサイズに合わせて拡大・縮小して画像全体を表示し、{cover} はコンテナーを画像で埋め、必要に応じて両端や下をクロップします。{stretch} は画像の高さと幅をコンテナーの 100% になるよう変更します。", - "expressionImage.functions.image.invalidImageModeErrorMessage": "「mode」は「{contain}」、「{cover}」、または「{stretch}」でなければなりません", - "expressionImage.functions.imageHelpText": "画像を表示します。画像アセットは{BASE64}データ{URL}として提供するか、部分式で渡します。", "xpack.canvas.functions.joinRows.args.columnHelpText": "値を抽出する列またはフィールド。", "xpack.canvas.functions.joinRows.args.distinctHelpText": "一意の値のみを抽出しますか?", "xpack.canvas.functions.joinRows.args.quoteHelpText": "各抽出された値を囲む引用符文字。", @@ -6723,11 +6719,6 @@ "xpack.canvas.functions.markdown.args.fontHelpText": "コンテンツの {CSS} フォントプロパティです。たとえば、{fontFamily} または {fontWeight} です。", "xpack.canvas.functions.markdown.args.openLinkHelpText": "新しいタブでリンクを開くためのtrue/false値。デフォルト値は「false」です。「true」に設定するとすべてのリンクが新しいタブで開くようになります。", "xpack.canvas.functions.markdownHelpText": "{MARKDOWN} テキストをレンダリングするエレメントを追加します。ヒント:単一の数字、メトリック、テキストの段落には {markdownFn} 関数を使います。", - "expressionMetric.functions.metric.args.labelFontHelpText": "ラベルの {CSS} フォントプロパティです。例:{FONT_FAMILY} または {FONT_WEIGHT}。", - "expressionMetric.functions.metric.args.labelHelpText": "メトリックを説明するテキストです。", - "expressionMetric.functions.metric.args.metricFontHelpText": "メトリックの {CSS} フォントプロパティです。例:{FONT_FAMILY} または {FONT_WEIGHT}。", - "expressionMetric.functions.metric.args.metricFormatHelpText": "{NUMERALJS} 形式の文字列。例:{example1} または {example2}。", - "expressionMetric.functions.metricHelpText": "ラベルの上に数字を表示します。", "xpack.canvas.functions.neq.args.valueHelpText": "{CONTEXT} と比較される値です。", "xpack.canvas.functions.neqHelpText": "{CONTEXT} が引数と等しくないかを戻します。", "xpack.canvas.functions.pie.args.fontHelpText": "ラベルの {CSS} フォントプロパティです。例:{FONT_FAMILY} または {FONT_WEIGHT}。", @@ -6959,8 +6950,6 @@ "expressionImage.renderer.image.helpDescription": "画像をレンダリングします", "xpack.canvas.renderer.markdown.displayName": "マークダウン", "xpack.canvas.renderer.markdown.helpDescription": "{MARKDOWN} インプットを使用して {HTML} を表示", - "expressionMetric.renderer.metric.displayName": "メトリック", - "expressionMetric.renderer.metric.helpDescription": "ラベルの上に数字をレンダリングします", "xpack.canvas.renderer.pie.displayName": "円グラフ", "xpack.canvas.renderer.pie.helpDescription": "データから円グラフをレンダリングします", "xpack.canvas.renderer.plot.displayName": "座標プロット", @@ -13270,7 +13259,6 @@ "xpack.ingestPipelines.pipelineEditor.setForm.overrideFieldHelpText": "有効にすると、既存のフィールド値を上書きします。無効にすると、{nullValue}フィールドのみを更新します。", "xpack.ingestPipelines.pipelineEditor.setForm.overrideFieldLabel": "無効化", "xpack.ingestPipelines.pipelineEditor.setForm.propertiesFieldHelpText": "追加するユーザー詳細情報。フォルトは{value}です。", - "xpack.ingestPipelines.pipelineEditor.setForm.valueFieldHelpText": "フィールドの値。空白の値は{emptyString}を設定します。", "xpack.ingestPipelines.pipelineEditor.setForm.valueFieldLabel": "値", "xpack.ingestPipelines.pipelineEditor.setSecurityUserForm.fieldNameField": "出力フィールド。", "xpack.ingestPipelines.pipelineEditor.setSecurityUserForm.propertiesFieldLabel": "プロパティ (任意) ", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 1a99ec15fb008..cc6393b640308 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -6743,10 +6743,6 @@ "xpack.canvas.functions.if.args.elseHelpText": "条件为 {BOOLEAN_FALSE} 时的返回值。未指定且条件未满足时,将返回原始 {CONTEXT}。", "xpack.canvas.functions.if.args.thenHelpText": "条件为 {BOOLEAN_TRUE} 时的返回值。未指定且条件满足时,将返回原始 {CONTEXT}。", "xpack.canvas.functions.ifHelpText": "执行条件逻辑。", - "expressionImage.functions.image.args.dataurlHelpText": "图像的 {https} {URL} 或 {BASE64} 数据 {URL}。", - "expressionImage.functions.image.args.modeHelpText": "{contain} 将显示整个图像,图像缩放至适合大小。{cover} 将使用该图像填充容器,根据需要在两边或底部裁剪图像。{stretch} 将图像的高和宽调整为容器的 100%。", - "expressionImage.functions.image.invalidImageModeErrorMessage": "“mode”必须为“{contain}”、“{cover}”或“{stretch}”", - "expressionImage.functions.imageHelpText": "显示图像。以 {BASE64} 数据 {URL} 的形式提供图像资产或传入子表达式。", "xpack.canvas.functions.joinRows.args.columnHelpText": "从其中提取值的列或字段。", "xpack.canvas.functions.joinRows.args.distinctHelpText": "仅提取唯一值?", "xpack.canvas.functions.joinRows.args.quoteHelpText": "要将每个提取的值引起来的引号字符。", @@ -6764,11 +6760,6 @@ "xpack.canvas.functions.markdown.args.fontHelpText": "内容的 {CSS} 字体属性。例如 {fontFamily} 或 {fontWeight}。", "xpack.canvas.functions.markdown.args.openLinkHelpText": "用于在新标签页中打开链接的 true 或 false 值。默认值为 `false`。设置为 `true` 时将在新标签页中打开所有链接。", "xpack.canvas.functions.markdownHelpText": "添加呈现 {MARKDOWN} 文本的元素。提示:将 {markdownFn} 函数用于单个数字、指标和文本段落。", - "expressionMetric.functions.metric.args.labelFontHelpText": "标签的 {CSS} 字体属性。例如 {FONT_FAMILY} 或 {FONT_WEIGHT}。", - "expressionMetric.functions.metric.args.labelHelpText": "描述指标的文本。", - "expressionMetric.functions.metric.args.metricFontHelpText": "指标的 {CSS} 字体属性。例如 {FONT_FAMILY} 或 {FONT_WEIGHT}。", - "expressionMetric.functions.metric.args.metricFormatHelpText": "{NUMERALJS} 格式字符串。例如 {example1} 或 {example2}。", - "expressionMetric.functions.metricHelpText": "在标签上显示数字。", "xpack.canvas.functions.neq.args.valueHelpText": "与 {CONTEXT} 比较的值。", "xpack.canvas.functions.neqHelpText": "返回 {CONTEXT} 是否不等于参数。", "xpack.canvas.functions.pie.args.fontHelpText": "标签的 {CSS} 字体属性。例如 {FONT_FAMILY} 或 {FONT_WEIGHT}。", @@ -7000,8 +6991,6 @@ "expressionImage.renderer.image.helpDescription": "呈现图像", "xpack.canvas.renderer.markdown.displayName": "Markdown", "xpack.canvas.renderer.markdown.helpDescription": "使用 {MARKDOWN} 输入呈现 {HTML}", - "expressionMetric.renderer.metric.displayName": "指标", - "expressionMetric.renderer.metric.helpDescription": "在标签上呈现数字", "xpack.canvas.renderer.pie.displayName": "饼图", "xpack.canvas.renderer.pie.helpDescription": "根据您的数据呈现饼图", "xpack.canvas.renderer.plot.displayName": "坐标图", @@ -13440,7 +13429,6 @@ "xpack.ingestPipelines.pipelineEditor.setForm.overrideFieldHelpText": "如果启用,则覆盖现有字段值。如果禁用,则仅更新 {nullValue} 字段。", "xpack.ingestPipelines.pipelineEditor.setForm.overrideFieldLabel": "覆盖", "xpack.ingestPipelines.pipelineEditor.setForm.propertiesFieldHelpText": "要添加的用户详情。默认为 {value}", - "xpack.ingestPipelines.pipelineEditor.setForm.valueFieldHelpText": "字段的值。空值将设置 {emptyString}。", "xpack.ingestPipelines.pipelineEditor.setForm.valueFieldLabel": "值", "xpack.ingestPipelines.pipelineEditor.setSecurityUserForm.fieldNameField": "输出字段。", "xpack.ingestPipelines.pipelineEditor.setSecurityUserForm.propertiesFieldLabel": "属性(可选)",