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] Custom processor form #66022

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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@
*/
import { i18n } from '@kbn/i18n';
import React, { FunctionComponent, useCallback, useEffect } from 'react';
import { EuiButton } from '@elastic/eui';
import { EuiButton, EuiHorizontalRule } from '@elastic/eui';

import { Form, useForm, FormDataProvider, OnFormUpdateArg } from '../../../../../shared_imports';

import { ProcessorInternal } from '../../types';

import { getProcessorForm } from './map_processor_type_to_form';
import { CommonProcessorFields, ProcessorTypeField } from './processors/common_fields';
import { Custom } from './processors/custom';

export type ProcessorSettingsFromOnSubmitArg = Omit<ProcessorInternal, 'id'>;

Expand All @@ -30,10 +31,10 @@ export const ProcessorSettingsForm: FunctionComponent<Props> = ({
const handleSubmit = useCallback(
(data: any, isValid: boolean) => {
if (isValid) {
const { type, ...options } = data;
const { type, customOptions, ...options } = data;
onSubmit({
type,
options,
options: customOptions ? customOptions : options,
});
}
},
Expand All @@ -58,33 +59,42 @@ export const ProcessorSettingsForm: FunctionComponent<Props> = ({
return (
<Form form={form}>
<ProcessorTypeField initialType={processor?.type} />

<EuiHorizontalRule />

<FormDataProvider pathsToWatch="type">
{({ type }) => {
let FormFields: FunctionComponent | null = null;
{({ type, customOptions, ...options }) => {
let formContent: React.ReactNode | undefined;

if (type) {
FormFields = getProcessorForm(type as any);
if (type?.length) {
const ProcessorFormFields = getProcessorForm(type as any);

// TODO: Handle this error in a different way
if (!FormFields) {
throw new Error(`Could not find form for type ${type}`);
if (ProcessorFormFields) {
formContent = (
<>
<ProcessorFormFields />
<CommonProcessorFields />
</>
);
} else {
formContent = <Custom defaultOptions={options} />;
}
}

return (
FormFields && (
return (
<>
<FormFields />
<CommonProcessorFields />
{formContent}
<EuiButton onClick={form.submit}>
{i18n.translate(
'xpack.ingestPipelines.pipelineEditor.settingsForm.submitButtonLabel',
{ defaultMessage: 'Submit' }
)}
</EuiButton>
</>
)
);
);
}

// If the user has not yet defined a type, we do not show any settings fields
return null;
}}
</FormDataProvider>
</Form>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
*/

import React, { FunctionComponent } from 'react';
import { i18n } from '@kbn/i18n';

import {
FormRow,
FieldConfig,
UseField,
FIELD_TYPES,
Expand All @@ -16,32 +17,39 @@ import {

const ignoreFailureConfig: FieldConfig = {
defaultValue: false,
label: 'Ignore Failure',
label: i18n.translate(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding these!

'xpack.ingestPipelines.pipelineEditor.commonFields.ignoreFailureFieldLabel',
{
defaultMessage: 'Ignore failure',
}
),
type: FIELD_TYPES.TOGGLE,
};

const ifConfig: FieldConfig = {
defaultValue: undefined,
label: 'If',
label: i18n.translate('xpack.ingestPipelines.pipelineEditor.commonFields.ifFieldLabel', {
defaultMessage: 'Condition (optional)',
}),
type: FIELD_TYPES.TEXT,
};

const tagConfig: FieldConfig = {
defaultValue: undefined,
label: 'Tag',
label: i18n.translate('xpack.ingestPipelines.pipelineEditor.commonFields.tagFieldLabel', {
defaultMessage: 'Tag (optional)',
}),
type: FIELD_TYPES.TEXT,
};

export const CommonProcessorFields: FunctionComponent = () => {
return (
<>
<FormRow title="Ignore Failure">
<UseField config={ignoreFailureConfig} component={ToggleField} path={'ignore_failure'} />
</FormRow>
<FormRow title="If">
<UseField config={ifConfig} component={Field} path={'if'} />
</FormRow>
<FormRow title="Tag">
<UseField config={tagConfig} component={Field} path={'tag'} />
</FormRow>
<UseField config={ignoreFailureConfig} component={ToggleField} path={'ignore_failure'} />

<UseField config={ifConfig} component={Field} path={'if'} />

<UseField config={tagConfig} component={Field} path={'tag'} />
</>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,13 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { i18n } from '@kbn/i18n';
import { EuiComboBox } from '@elastic/eui';
import React, { FunctionComponent } from 'react';
import {
FIELD_TYPES,
FieldConfig,
UseField,
fieldValidators,
FormRow,
ComboBoxField,
} from '../../../../../../../shared_imports';
import { types } from '../../map_processor_type_to_form';

Expand All @@ -24,8 +23,17 @@ const { emptyField } = fieldValidators;
const typeConfig: FieldConfig = {
type: FIELD_TYPES.COMBO_BOX,
label: i18n.translate('xpack.ingestPipelines.pipelineEditor.typeField.typeFieldLabel', {
defaultMessage: 'Type',
defaultMessage: 'Processor',
}),
deserializer: (value: string | undefined) => {
if (value) {
return [value];
}
return [];
},
serializer: (value: string[]) => {
return value[0];
},
validations: [
{
validator: emptyField(
Expand All @@ -39,27 +47,21 @@ const typeConfig: FieldConfig = {

export const ProcessorTypeField: FunctionComponent<Props> = ({ initialType }) => {
return (
<FormRow
title={i18n.translate('xpack.ingestPipelines.pipelineEditor.typeFieldTitle', {
defaultMessage: 'Type',
})}
>
<UseField config={typeConfig} path={'type'} defaultValue={initialType}>
{typeField => {
return (
<EuiComboBox
onChange={([selected]) => typeField.setValue(selected?.value)}
selectedOptions={
typeField.value
? [{ value: typeField.value as string, label: typeField.value as string }]
: []
}
singleSelection={{ asPlainText: true }}
options={types.map(type => ({ label: type, value: type }))}
/>
);
}}
</UseField>
</FormRow>
<UseField
config={typeConfig}
defaultValue={initialType}
Copy link
Contributor

@sebelga sebelga May 19, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a note: by setting the defaultValue manually you lose the benefit to read it from the form defaultValue object (used in "Edit" mode). Not sure if this was intentional so leaving a comment. @alisonelizabeth @jloleysens

path="type"
component={ComboBoxField}
componentProps={{
euiFieldProps: {
fullWidth: true,
options: types.map(type => ({ label: type, value: type })),
noSuggestions: false,
singleSelection: {
asPlainText: true,
},
},
}}
/>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import React, { FunctionComponent } from 'react';
import { i18n } from '@kbn/i18n';

import {
FieldConfig,
FIELD_TYPES,
fieldValidators,
UseField,
JsonEditorField,
} from '../../../../../../shared_imports';

const { emptyField, isJsonField } = fieldValidators;

const customConfig: FieldConfig = {
type: FIELD_TYPES.TEXT,
label: i18n.translate('xpack.ingestPipelines.pipelineEditor.customForm.optionsFieldLabel', {
defaultMessage: 'Configuration options',
}),
serializer: (value: string) => {
try {
return JSON.parse(value);
} catch (error) {
// swallow error and return non-parsed value;
return value;
}
},
deserializer: (value: any) => {
if (value === '') {
return '{\n\n}';
}
return JSON.stringify(value, null, 2);
},
validations: [
{
validator: emptyField(
i18n.translate(
'xpack.ingestPipelines.pipelineEditor.customForm.configurationRequiredError',
{
defaultMessage: 'Configuration options are required.',
}
)
),
},
{
validator: isJsonField(
i18n.translate('xpack.ingestPipelines.pipelineEditor.customForm.invalidJsonError', {
defaultMessage: 'The input is not valid.',
})
),
},
],
};

interface Props {
defaultOptions?: any;
}

/**
* This is a catch-all component to support settings for custom processors
* or existing processors not yet supported by the UI.
*
* We store the settings in a field called "customOptions"
**/
export const Custom: FunctionComponent<Props> = ({ defaultOptions }) => {
return (
<UseField
path="customOptions"
component={JsonEditorField}
config={customConfig}
defaultValue={defaultOptions}
componentProps={{
euiCodeEditorProps: {
height: '300px',
'aria-label': i18n.translate(
'xpack.ingestPipelines.pipelineEditor.customForm.optionsFieldAriaLabel',
{
defaultMessage: 'Configuration options JSON editor',
}
),
},
}}
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
FieldConfig,
FIELD_TYPES,
fieldValidators,
FormRow,
ToggleField,
UseField,
Field,
} from '../../../../../../shared_imports';
Expand Down Expand Up @@ -66,33 +66,33 @@ const replacementConfig: FieldConfig = {
],
};

const targetConfig: FieldConfig = {
type: FIELD_TYPES.TEXT,
label: i18n.translate('xpack.ingestPipelines.pipelineEditor.gsubForm.targetFieldLabel', {
defaultMessage: 'Target field (optional)',
}),
};

const ignoreMissingConfig: FieldConfig = {
defaultValue: false,
label: i18n.translate('xpack.ingestPipelines.pipelineEditor.gsubForm.ignoreMissingFieldLabel', {
defaultMessage: 'Ignore missing',
}),
type: FIELD_TYPES.TOGGLE,
};

export const Gsub: FunctionComponent = () => {
return (
<>
<FormRow
title={i18n.translate('xpack.ingestPipelines.pipelineEditor.gsubForm.fieldFieldTitle', {
defaultMessage: 'Field',
})}
>
<UseField config={fieldConfig} component={Field} path="field" />
</FormRow>
<FormRow
title={i18n.translate('xpack.ingestPipelines.pipelineEditor.gsubForm.patternFieldTitle', {
defaultMessage: 'Pattern',
})}
>
<UseField config={patternConfig} component={Field} path="pattern" />
</FormRow>
<FormRow
title={i18n.translate(
'xpack.ingestPipelines.pipelineEditor.gsubForm.replacementFieldTitle',
{
defaultMessage: 'Replacement',
}
)}
>
<UseField config={replacementConfig} component={Field} path="replacement" />
</FormRow>
<UseField config={fieldConfig} component={Field} path="field" />

<UseField config={patternConfig} component={Field} path="pattern" />

<UseField config={replacementConfig} component={Field} path="replacement" />

<UseField config={targetConfig} component={Field} path="target_field" />

<UseField config={ignoreMissingConfig} component={ToggleField} path="ignore_missing" />
</>
);
};
Loading