Skip to content

Commit

Permalink
Convert optional inputs to toggle state
Browse files Browse the repository at this point in the history
  • Loading branch information
sabarasaba committed Jul 20, 2021
1 parent 4a8e92b commit cbd9dd7
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 108 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -180,5 +180,5 @@ type TestSubject =
| 'fieldsValueField.input'
| 'saltValueField.input'
| 'methodsValueField'
| 'copyFromField.input'
| 'copyFromInput'
| 'trimSwitch.input';
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,7 @@ describe('Processor: Set', () => {

// Expect form error as "field" is required parameter
expect(form.getErrorsMessages()).toEqual([
'Either this field or copy_from should be specified.',
'Either this field or value should be specified.',
'A field value is required.',
'A field value is required.',
]);
});
Expand All @@ -79,19 +78,33 @@ describe('Processor: Set', () => {
});
});

test('allows to set either value or copy_from', async () => {
const { find, form } = testBed;
test('allows to set just internal_networks_field or internal_networks', async () => {
const {
actions: { saveNewProcessor },
form,
find,
} = testBed;

expect(find('valueFieldInput').exists()).toBe(true);
expect(find('copyFromField.input').exists()).toBe(true);
// Add required fields
form.setInputValue('fieldNameField.input', 'field_1');

// Set value field
form.setInputValue('valueFieldInput', 'value');
expect(find('copyFromField.input').props().disabled).toBe(true);

form.setInputValue('valueFieldInput', '');
form.setInputValue('copyFromField.input', 'copy_from');
expect(find('valueFieldInput').props().disabled).toBe(true);
// 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',
});
});

test('should allow to set mediaType when value is a template snippet', async () => {
const {
actions: { saveNewProcessor },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
* 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 } from '@elastic/eui';
import { EuiCode, EuiButtonEmpty } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';

import {
Expand All @@ -18,85 +18,26 @@ 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]>;
};

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,
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. Used only when {field} is not specified."
values={{
field: <EuiCode>{'copy_from'}</EuiCode>,
}}
/>
),
fieldsToValidateOnChange: ['fields.copy_from', 'fields.value'],
validations: [
{
validator: ({ value, path, formData }) => {
if (isEmpty(value) && isEmpty(formData['fields.copy_from'])) {
return {
path,
message: i18n.translate(
'xpack.ingestPipelines.pipelineEditor.setForm.valueFieldError',
{
defaultMessage: 'Either this field or copy_from should be specified.',
}
),
};
}
},
},
],
},
copy_from: {
type: FIELD_TYPES.TEXT,
serializer: from.emptyStringToUndefined,
label: i18n.translate('xpack.ingestPipelines.pipelineEditor.setForm.copyFromFieldLabel', {
defaultMessage: 'Copy from',
}),
helpText: (
<FormattedMessage
id="xpack.ingestPipelines.pipelineEditor.setForm.copyFromFieldHelpText"
defaultMessage="The origin field which will be copied to {field}. Used only when {value} is not specified."
values={{
field: <EuiCode>{'field'}</EuiCode>,
value: <EuiCode>{'value'}</EuiCode>,
}}
/>
),
fieldsToValidateOnChange: ['fields.copy_from', 'fields.value'],
validations: [
{
validator: ({ value, path, formData }) => {
if (isEmpty(value) && isEmpty(formData['fields.value'])) {
return {
path,
message: i18n.translate(
'xpack.ingestPipelines.pipelineEditor.setForm.copyFromFieldError',
{
defaultMessage: 'Either this field or value should be specified.',
}
),
};
}
},
},
],
},
mediaType: {
type: FIELD_TYPES.SELECT,
defaultValue: 'application/json',
Expand All @@ -111,7 +52,6 @@ const fieldsConfig: FieldsConfig = {
/>
),
},
/* Optional fields config */
override: {
type: FIELD_TYPES.TOGGLE,
defaultValue: true,
Expand Down Expand Up @@ -155,12 +95,115 @@ const fieldsConfig: FieldsConfig = {
},
};

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: 'A field value is required.' };
}
},
},
],
},
labelAppend: (
<EuiButtonEmpty size="xs" onClick={toggleCustom} data-test-subj="toggleCustomField">
{i18n.translate('xpack.ingestPipelines.pipelineEditor.useCopyFromLabel', {
defaultMessage: 'Use copy_from field',
})}
</EuiButtonEmpty>
),
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: 'A field value is required.' };
}
},
},
],
label: i18n.translate('xpack.ingestPipelines.pipelineEditor.setForm.copyFromFieldLabel', {
defaultMessage: 'Copy from',
}),
helpText: (
<FormattedMessage
id="xpack.ingestPipelines.pipelineEditor.setForm.copyFromFieldHelpText"
defaultMessage="The origin field which will be copied to {field}."
values={{
field: <EuiCode>{'field'}</EuiCode>,
}}
/>
),
},
labelAppend: (
<EuiButtonEmpty size="xs" onClick={toggleCustom} data-test-subj="toggleCustomField">
{i18n.translate('xpack.ingestPipelines.pipelineEditor.useValueLabel', {
defaultMessage: 'Use value field',
})}
</EuiButtonEmpty>
),
key: 'copy_from',
},
});

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

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

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

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

return (
<>
<FieldNameField
Expand All @@ -169,17 +212,7 @@ export const SetProcessor: FunctionComponent = () => {
})}
/>

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

{hasTemplateSnippet(fields?.value) && (
<UseField
Expand Down Expand Up @@ -208,18 +241,6 @@ export const SetProcessor: FunctionComponent = () => {
/>
)}

<UseField
config={fieldsConfig.copy_from}
component={Field}
path="fields.copy_from"
componentProps={{
euiFieldProps: {
disabled: !!fields?.value,
},
}}
data-test-subj="copyFromField"
/>

<UseField
config={fieldsConfig.override}
component={ToggleField}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,8 @@ export const PipelineProcessorsContextProvider: FunctionComponent<Props> = ({
// We manually add fields that we **don't** want to be treated as "unknownOptions"
'internal_networks',
'internal_networks_field',
'value',
'copy_from',
];

// The processor that we are updating may have options configured the UI does not know about
Expand Down

0 comments on commit cbd9dd7

Please sign in to comment.