[Ingest Pipelines] Load from json (#70297)
* WiP load from json modal ready, need to refactor more stuff * First iteration of load from JSON functionality - refactored the pipeline processsors editor components for portability - added CIT for load from json component * added comment * update deserialize with tests and make it more fault tolerant * use flyout footer * remove console.error and make the json editor a lot shorter * address PR feedback - Update form schema and form schema types - simplify the save handler - refactor processors_title to processors_header * remove unused translations Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
f1888cd978
commit
5159635d5b
37 changed files with 931 additions and 594 deletions
|
@ -8,7 +8,7 @@ import React, { FunctionComponent } from 'react';
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
import { FormattedMessage } from '@kbn/i18n/react';
|
import { FormattedMessage } from '@kbn/i18n/react';
|
||||||
|
|
||||||
import { usePipelineProcessorsContext } from '../context';
|
import { usePipelineProcessorsContext } from '../pipeline_processors_editor/context';
|
||||||
|
|
||||||
export const OnFailureProcessorsTitle: FunctionComponent = () => {
|
export const OnFailureProcessorsTitle: FunctionComponent = () => {
|
||||||
const { links } = usePipelineProcessorsContext();
|
const { links } = usePipelineProcessorsContext();
|
|
@ -9,19 +9,18 @@ import { FormattedMessage } from '@kbn/i18n/react';
|
||||||
import { EuiButton, EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
|
import { EuiButton, EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
|
||||||
|
|
||||||
import { useForm, Form, FormConfig } from '../../../shared_imports';
|
import { useForm, Form, FormConfig } from '../../../shared_imports';
|
||||||
import { Pipeline } from '../../../../common/types';
|
import { Pipeline, Processor } from '../../../../common/types';
|
||||||
|
|
||||||
import {
|
import './pipeline_form.scss';
|
||||||
OnUpdateHandlerArg,
|
|
||||||
OnUpdateHandler,
|
import { OnUpdateHandlerArg, OnUpdateHandler } from '../pipeline_processors_editor';
|
||||||
SerializeResult,
|
|
||||||
} from '../pipeline_processors_editor';
|
|
||||||
|
|
||||||
import { PipelineRequestFlyout } from './pipeline_request_flyout';
|
import { PipelineRequestFlyout } from './pipeline_request_flyout';
|
||||||
import { PipelineTestFlyout } from './pipeline_test_flyout';
|
import { PipelineTestFlyout } from './pipeline_test_flyout';
|
||||||
import { PipelineFormFields } from './pipeline_form_fields';
|
import { PipelineFormFields } from './pipeline_form_fields';
|
||||||
import { PipelineFormError } from './pipeline_form_error';
|
import { PipelineFormError } from './pipeline_form_error';
|
||||||
import { pipelineFormSchema } from './schema';
|
import { pipelineFormSchema } from './schema';
|
||||||
|
import { PipelineForm as IPipelineForm } from './types';
|
||||||
|
|
||||||
export interface PipelineFormProps {
|
export interface PipelineFormProps {
|
||||||
onSave: (pipeline: Pipeline) => void;
|
onSave: (pipeline: Pipeline) => void;
|
||||||
|
@ -32,14 +31,15 @@ export interface PipelineFormProps {
|
||||||
isEditing?: boolean;
|
isEditing?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const defaultFormValue: Pipeline = Object.freeze({
|
||||||
|
name: '',
|
||||||
|
description: '',
|
||||||
|
processors: [],
|
||||||
|
on_failure: [],
|
||||||
|
});
|
||||||
|
|
||||||
export const PipelineForm: React.FunctionComponent<PipelineFormProps> = ({
|
export const PipelineForm: React.FunctionComponent<PipelineFormProps> = ({
|
||||||
defaultValue = {
|
defaultValue = defaultFormValue,
|
||||||
name: '',
|
|
||||||
description: '',
|
|
||||||
processors: [],
|
|
||||||
on_failure: [],
|
|
||||||
version: '',
|
|
||||||
},
|
|
||||||
onSave,
|
onSave,
|
||||||
isSaving,
|
isSaving,
|
||||||
saveError,
|
saveError,
|
||||||
|
@ -50,34 +50,42 @@ export const PipelineForm: React.FunctionComponent<PipelineFormProps> = ({
|
||||||
|
|
||||||
const [isTestingPipeline, setIsTestingPipeline] = useState<boolean>(false);
|
const [isTestingPipeline, setIsTestingPipeline] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const {
|
||||||
|
processors: initialProcessors,
|
||||||
|
on_failure: initialOnFailureProcessors,
|
||||||
|
...defaultFormValues
|
||||||
|
} = defaultValue;
|
||||||
|
|
||||||
|
const [processorsState, setProcessorsState] = useState<{
|
||||||
|
processors: Processor[];
|
||||||
|
onFailure?: Processor[];
|
||||||
|
}>({
|
||||||
|
processors: initialProcessors,
|
||||||
|
onFailure: initialOnFailureProcessors,
|
||||||
|
});
|
||||||
|
|
||||||
const processorStateRef = useRef<OnUpdateHandlerArg>();
|
const processorStateRef = useRef<OnUpdateHandlerArg>();
|
||||||
|
|
||||||
const handleSave: FormConfig['onSubmit'] = async (formData, isValid) => {
|
const handleSave: FormConfig<IPipelineForm>['onSubmit'] = async (formData, isValid) => {
|
||||||
let override: SerializeResult | undefined;
|
|
||||||
|
|
||||||
if (!isValid) {
|
if (!isValid) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (processorStateRef.current) {
|
if (processorStateRef.current) {
|
||||||
const processorsState = processorStateRef.current;
|
const state = processorStateRef.current;
|
||||||
if (await processorsState.validate()) {
|
if (await state.validate()) {
|
||||||
override = processorsState.getData();
|
onSave({ ...formData, ...state.getData() });
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onSave({ ...formData, ...(override || {}) } as Pipeline);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleTestPipelineClick = () => {
|
const handleTestPipelineClick = () => {
|
||||||
setIsTestingPipeline(true);
|
setIsTestingPipeline(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const { form } = useForm({
|
const { form } = useForm<IPipelineForm>({
|
||||||
schema: pipelineFormSchema,
|
schema: pipelineFormSchema,
|
||||||
defaultValue,
|
defaultValue: defaultFormValues,
|
||||||
onSubmit: handleSave,
|
onSubmit: handleSave,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -121,9 +129,12 @@ export const PipelineForm: React.FunctionComponent<PipelineFormProps> = ({
|
||||||
|
|
||||||
{/* All form fields */}
|
{/* All form fields */}
|
||||||
<PipelineFormFields
|
<PipelineFormFields
|
||||||
|
onLoadJson={({ processors, on_failure: onFailure }) => {
|
||||||
|
setProcessorsState({ processors, onFailure });
|
||||||
|
}}
|
||||||
onEditorFlyoutOpen={onEditorFlyoutOpen}
|
onEditorFlyoutOpen={onEditorFlyoutOpen}
|
||||||
initialProcessors={defaultValue.processors}
|
processors={processorsState.processors}
|
||||||
initialOnFailureProcessors={defaultValue.on_failure}
|
onFailure={processorsState.onFailure}
|
||||||
onProcessorsUpdate={onProcessorsChangeHandler}
|
onProcessorsUpdate={onProcessorsChangeHandler}
|
||||||
hasVersion={Boolean(defaultValue.version)}
|
hasVersion={Boolean(defaultValue.version)}
|
||||||
isTestButtonDisabled={isTestingPipeline || form.isValid === false}
|
isTestButtonDisabled={isTestingPipeline || form.isValid === false}
|
||||||
|
|
|
@ -6,17 +6,27 @@
|
||||||
|
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { FormattedMessage } from '@kbn/i18n/react';
|
import { FormattedMessage } from '@kbn/i18n/react';
|
||||||
import { EuiSpacer, EuiSwitch } from '@elastic/eui';
|
import { EuiSpacer, EuiSwitch, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||||
|
|
||||||
import { Processor } from '../../../../common/types';
|
import { Processor } from '../../../../common/types';
|
||||||
import { FormDataProvider } from '../../../shared_imports';
|
|
||||||
import { PipelineProcessorsEditor, OnUpdateHandler } from '../pipeline_processors_editor';
|
|
||||||
|
|
||||||
import { getUseField, getFormRow, Field, useKibana } from '../../../shared_imports';
|
import { getUseField, getFormRow, Field, useKibana } from '../../../shared_imports';
|
||||||
|
|
||||||
|
import {
|
||||||
|
PipelineProcessorsContextProvider,
|
||||||
|
GlobalOnFailureProcessorsEditor,
|
||||||
|
ProcessorsEditor,
|
||||||
|
OnUpdateHandler,
|
||||||
|
OnDoneLoadJsonHandler,
|
||||||
|
} from '../pipeline_processors_editor';
|
||||||
|
|
||||||
|
import { ProcessorsHeader } from './processors_header';
|
||||||
|
import { OnFailureProcessorsTitle } from './on_failure_processors_title';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
initialProcessors: Processor[];
|
processors: Processor[];
|
||||||
initialOnFailureProcessors?: Processor[];
|
onFailure?: Processor[];
|
||||||
|
onLoadJson: OnDoneLoadJsonHandler;
|
||||||
onProcessorsUpdate: OnUpdateHandler;
|
onProcessorsUpdate: OnUpdateHandler;
|
||||||
hasVersion: boolean;
|
hasVersion: boolean;
|
||||||
isTestButtonDisabled: boolean;
|
isTestButtonDisabled: boolean;
|
||||||
|
@ -29,8 +39,9 @@ const UseField = getUseField({ component: Field });
|
||||||
const FormRow = getFormRow({ titleTag: 'h3' });
|
const FormRow = getFormRow({ titleTag: 'h3' });
|
||||||
|
|
||||||
export const PipelineFormFields: React.FunctionComponent<Props> = ({
|
export const PipelineFormFields: React.FunctionComponent<Props> = ({
|
||||||
initialProcessors,
|
processors,
|
||||||
initialOnFailureProcessors,
|
onFailure,
|
||||||
|
onLoadJson,
|
||||||
onProcessorsUpdate,
|
onProcessorsUpdate,
|
||||||
isEditing,
|
isEditing,
|
||||||
hasVersion,
|
hasVersion,
|
||||||
|
@ -113,30 +124,37 @@ export const PipelineFormFields: React.FunctionComponent<Props> = ({
|
||||||
</FormRow>
|
</FormRow>
|
||||||
|
|
||||||
{/* Pipeline Processors Editor */}
|
{/* Pipeline Processors Editor */}
|
||||||
<FormDataProvider pathsToWatch={['processors', 'on_failure']}>
|
|
||||||
{({ processors, on_failure: onFailure }) => {
|
|
||||||
const processorProp =
|
|
||||||
typeof processors === 'string' && processors
|
|
||||||
? JSON.parse(processors)
|
|
||||||
: initialProcessors ?? [];
|
|
||||||
|
|
||||||
const onFailureProp =
|
<PipelineProcessorsContextProvider
|
||||||
typeof onFailure === 'string' && onFailure
|
onFlyoutOpen={onEditorFlyoutOpen}
|
||||||
? JSON.parse(onFailure)
|
links={{ esDocsBasePath: services.documentation.getEsDocsBasePath() }}
|
||||||
: initialOnFailureProcessors ?? [];
|
onUpdate={onProcessorsUpdate}
|
||||||
|
value={{ processors, onFailure }}
|
||||||
return (
|
>
|
||||||
<PipelineProcessorsEditor
|
<div className="pipelineProcessorsEditor">
|
||||||
onFlyoutOpen={onEditorFlyoutOpen}
|
<EuiFlexGroup gutterSize="m" responsive={false} direction="column">
|
||||||
esDocsBasePath={services.documentation.getEsDocsBasePath()}
|
<EuiFlexItem grow={false}>
|
||||||
isTestButtonDisabled={isTestButtonDisabled}
|
<ProcessorsHeader
|
||||||
onTestPipelineClick={onTestPipelineClick}
|
onLoadJson={onLoadJson}
|
||||||
onUpdate={onProcessorsUpdate}
|
onTestPipelineClick={onTestPipelineClick}
|
||||||
value={{ processors: processorProp, onFailure: onFailureProp }}
|
isTestButtonDisabled={isTestButtonDisabled}
|
||||||
/>
|
/>
|
||||||
);
|
</EuiFlexItem>
|
||||||
}}
|
<EuiFlexItem grow={false}>
|
||||||
</FormDataProvider>
|
<ProcessorsEditor />
|
||||||
|
</EuiFlexItem>
|
||||||
|
<EuiFlexItem>
|
||||||
|
<EuiSpacer size="s" />
|
||||||
|
</EuiFlexItem>
|
||||||
|
<EuiFlexItem grow={false}>
|
||||||
|
<OnFailureProcessorsTitle />
|
||||||
|
</EuiFlexItem>
|
||||||
|
<EuiFlexItem grow={false}>
|
||||||
|
<GlobalOnFailureProcessorsEditor />
|
||||||
|
</EuiFlexItem>
|
||||||
|
</EuiFlexGroup>
|
||||||
|
</div>
|
||||||
|
</PipelineProcessorsContextProvider>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -9,22 +9,26 @@ import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiLink, EuiText, EuiTitle } from
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
import { FormattedMessage } from '@kbn/i18n/react';
|
import { FormattedMessage } from '@kbn/i18n/react';
|
||||||
|
|
||||||
import { usePipelineProcessorsContext } from '../context';
|
import { usePipelineProcessorsContext } from '../pipeline_processors_editor/context';
|
||||||
|
|
||||||
|
import { LoadFromJsonButton, OnDoneLoadJsonHandler } from '../pipeline_processors_editor';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
onTestPipelineClick: () => void;
|
onTestPipelineClick: () => void;
|
||||||
isTestButtonDisabled: boolean;
|
isTestButtonDisabled: boolean;
|
||||||
|
onLoadJson: OnDoneLoadJsonHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ProcessorsTitleAndTestButton: FunctionComponent<Props> = ({
|
export const ProcessorsHeader: FunctionComponent<Props> = ({
|
||||||
onTestPipelineClick,
|
onTestPipelineClick,
|
||||||
isTestButtonDisabled,
|
isTestButtonDisabled,
|
||||||
|
onLoadJson,
|
||||||
}) => {
|
}) => {
|
||||||
const { links } = usePipelineProcessorsContext();
|
const { links } = usePipelineProcessorsContext();
|
||||||
return (
|
return (
|
||||||
<EuiFlexGroup
|
<EuiFlexGroup
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
gutterSize="none"
|
gutterSize="s"
|
||||||
justifyContent="spaceBetween"
|
justifyContent="spaceBetween"
|
||||||
responsive={false}
|
responsive={false}
|
||||||
>
|
>
|
||||||
|
@ -55,6 +59,9 @@ export const ProcessorsTitleAndTestButton: FunctionComponent<Props> = ({
|
||||||
/>
|
/>
|
||||||
</EuiText>
|
</EuiText>
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
|
<EuiFlexItem grow={false}>
|
||||||
|
<LoadFromJsonButton onDone={onLoadJson} />
|
||||||
|
</EuiFlexItem>
|
||||||
<EuiFlexItem grow={false}>
|
<EuiFlexItem grow={false}>
|
||||||
<EuiButton
|
<EuiButton
|
||||||
data-test-subj="testPipelineButton"
|
data-test-subj="testPipelineButton"
|
|
@ -0,0 +1,44 @@
|
||||||
|
/*
|
||||||
|
* 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 { i18n } from '@kbn/i18n';
|
||||||
|
import { FormSchema, FIELD_TYPES, fieldValidators, fieldFormatters } from '../../../shared_imports';
|
||||||
|
|
||||||
|
import { PipelineForm } from './types';
|
||||||
|
|
||||||
|
const { emptyField } = fieldValidators;
|
||||||
|
const { toInt } = fieldFormatters;
|
||||||
|
|
||||||
|
export const pipelineFormSchema: FormSchema<PipelineForm> = {
|
||||||
|
name: {
|
||||||
|
type: FIELD_TYPES.TEXT,
|
||||||
|
label: i18n.translate('xpack.ingestPipelines.form.nameFieldLabel', {
|
||||||
|
defaultMessage: 'Name',
|
||||||
|
}),
|
||||||
|
validations: [
|
||||||
|
{
|
||||||
|
validator: emptyField(
|
||||||
|
i18n.translate('xpack.ingestPipelines.form.pipelineNameRequiredError', {
|
||||||
|
defaultMessage: 'Name is required.',
|
||||||
|
})
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
type: FIELD_TYPES.TEXTAREA,
|
||||||
|
label: i18n.translate('xpack.ingestPipelines.form.descriptionFieldLabel', {
|
||||||
|
defaultMessage: 'Description (optional)',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
version: {
|
||||||
|
type: FIELD_TYPES.NUMBER,
|
||||||
|
label: i18n.translate('xpack.ingestPipelines.form.versionFieldLabel', {
|
||||||
|
defaultMessage: 'Version (optional)',
|
||||||
|
}),
|
||||||
|
formatters: [toInt],
|
||||||
|
},
|
||||||
|
};
|
|
@ -1,138 +0,0 @@
|
||||||
/*
|
|
||||||
* 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 from 'react';
|
|
||||||
|
|
||||||
import { i18n } from '@kbn/i18n';
|
|
||||||
import { FormattedMessage } from '@kbn/i18n/react';
|
|
||||||
import { EuiCode } from '@elastic/eui';
|
|
||||||
|
|
||||||
import { FormSchema, FIELD_TYPES, fieldValidators, fieldFormatters } from '../../../shared_imports';
|
|
||||||
import { parseJson, stringifyJson } from '../../lib';
|
|
||||||
|
|
||||||
const { emptyField, isJsonField } = fieldValidators;
|
|
||||||
const { toInt } = fieldFormatters;
|
|
||||||
|
|
||||||
export const pipelineFormSchema: FormSchema = {
|
|
||||||
name: {
|
|
||||||
type: FIELD_TYPES.TEXT,
|
|
||||||
label: i18n.translate('xpack.ingestPipelines.form.nameFieldLabel', {
|
|
||||||
defaultMessage: 'Name',
|
|
||||||
}),
|
|
||||||
validations: [
|
|
||||||
{
|
|
||||||
validator: emptyField(
|
|
||||||
i18n.translate('xpack.ingestPipelines.form.pipelineNameRequiredError', {
|
|
||||||
defaultMessage: 'Name is required.',
|
|
||||||
})
|
|
||||||
),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
description: {
|
|
||||||
type: FIELD_TYPES.TEXTAREA,
|
|
||||||
label: i18n.translate('xpack.ingestPipelines.form.descriptionFieldLabel', {
|
|
||||||
defaultMessage: 'Description (optional)',
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
processors: {
|
|
||||||
label: i18n.translate('xpack.ingestPipelines.form.processorsFieldLabel', {
|
|
||||||
defaultMessage: 'Processors',
|
|
||||||
}),
|
|
||||||
helpText: (
|
|
||||||
<FormattedMessage
|
|
||||||
id="xpack.ingestPipelines.form.processorsFieldHelpText"
|
|
||||||
defaultMessage="Use JSON format: {code}"
|
|
||||||
values={{
|
|
||||||
code: (
|
|
||||||
<EuiCode>
|
|
||||||
{JSON.stringify([
|
|
||||||
{
|
|
||||||
set: {
|
|
||||||
field: 'foo',
|
|
||||||
value: 'bar',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
])}
|
|
||||||
</EuiCode>
|
|
||||||
),
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
serializer: parseJson,
|
|
||||||
deserializer: stringifyJson,
|
|
||||||
validations: [
|
|
||||||
{
|
|
||||||
validator: emptyField(
|
|
||||||
i18n.translate('xpack.ingestPipelines.form.processorsRequiredError', {
|
|
||||||
defaultMessage: 'Processors are required.',
|
|
||||||
})
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
validator: isJsonField(
|
|
||||||
i18n.translate('xpack.ingestPipelines.form.processorsJsonError', {
|
|
||||||
defaultMessage: 'The input is not valid.',
|
|
||||||
})
|
|
||||||
),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
on_failure: {
|
|
||||||
label: i18n.translate('xpack.ingestPipelines.form.onFailureFieldLabel', {
|
|
||||||
defaultMessage: 'Failure processors (optional)',
|
|
||||||
}),
|
|
||||||
helpText: (
|
|
||||||
<FormattedMessage
|
|
||||||
id="xpack.ingestPipelines.form.onFailureFieldHelpText"
|
|
||||||
defaultMessage="Use JSON format: {code}"
|
|
||||||
values={{
|
|
||||||
code: (
|
|
||||||
<EuiCode>
|
|
||||||
{JSON.stringify([
|
|
||||||
{
|
|
||||||
set: {
|
|
||||||
field: '_index',
|
|
||||||
value: 'failed-{{ _index }}',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
])}
|
|
||||||
</EuiCode>
|
|
||||||
),
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
serializer: (value) => {
|
|
||||||
const result = parseJson(value);
|
|
||||||
// If an empty array was passed, strip out this value entirely.
|
|
||||||
if (!result.length) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
},
|
|
||||||
deserializer: stringifyJson,
|
|
||||||
validations: [
|
|
||||||
{
|
|
||||||
validator: (validationArg) => {
|
|
||||||
if (!validationArg.value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
return isJsonField(
|
|
||||||
i18n.translate('xpack.ingestPipelines.form.onFailureProcessorsJsonError', {
|
|
||||||
defaultMessage: 'The input is not valid.',
|
|
||||||
})
|
|
||||||
)(validationArg);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
version: {
|
|
||||||
type: FIELD_TYPES.NUMBER,
|
|
||||||
label: i18n.translate('xpack.ingestPipelines.form.versionFieldLabel', {
|
|
||||||
defaultMessage: 'Version (optional)',
|
|
||||||
}),
|
|
||||||
formatters: [toInt],
|
|
||||||
},
|
|
||||||
};
|
|
|
@ -7,3 +7,5 @@
|
||||||
import { Pipeline } from '../../../../common/types';
|
import { Pipeline } from '../../../../common/types';
|
||||||
|
|
||||||
export type ReadProcessorsFunction = () => Pick<Pipeline, 'processors' | 'on_failure'>;
|
export type ReadProcessorsFunction = () => Pick<Pipeline, 'processors' | 'on_failure'>;
|
||||||
|
|
||||||
|
export type PipelineForm = Omit<Pipeline, 'processors' | 'on_failure'>;
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
# Pipeline Processors Editor
|
||||||
|
|
||||||
|
This component provides a way to visually build and manage an ingest
|
||||||
|
pipeline.
|
||||||
|
|
||||||
|
# API
|
||||||
|
|
||||||
|
## Editor components
|
||||||
|
|
||||||
|
The top-level API consists of 3 pieces that enable the maximum amount
|
||||||
|
of flexibility for consuming code to determine overall layout.
|
||||||
|
|
||||||
|
- PipelineProcessorsEditorContext
|
||||||
|
- ProcessorsEditor
|
||||||
|
- GlobalOnFailureProcessorsEditor
|
||||||
|
|
||||||
|
The editor components must be wrapped inside of the context component
|
||||||
|
as this is where the shared processors state is contained.
|
||||||
|
|
||||||
|
## Load JSON button
|
||||||
|
|
||||||
|
This component is totally standalone. It gives users a button that
|
||||||
|
presents a modal for loading a pipeline. It does some basic
|
||||||
|
validation on the JSON to ensure that it is correct.
|
|
@ -6,7 +6,12 @@
|
||||||
import { act } from 'react-dom/test-utils';
|
import { act } from 'react-dom/test-utils';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { registerTestBed, TestBed } from '../../../../../../../test_utils';
|
import { registerTestBed, TestBed } from '../../../../../../../test_utils';
|
||||||
import { PipelineProcessorsEditor, Props } from '../pipeline_processors_editor.container';
|
import {
|
||||||
|
PipelineProcessorsContextProvider,
|
||||||
|
Props,
|
||||||
|
ProcessorsEditor,
|
||||||
|
GlobalOnFailureProcessorsEditor,
|
||||||
|
} from '../';
|
||||||
|
|
||||||
jest.mock('@elastic/eui', () => {
|
jest.mock('@elastic/eui', () => {
|
||||||
const original = jest.requireActual('@elastic/eui');
|
const original = jest.requireActual('@elastic/eui');
|
||||||
|
@ -55,9 +60,16 @@ jest.mock('react-virtualized', () => {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const testBedSetup = registerTestBed<TestSubject>(PipelineProcessorsEditor, {
|
const testBedSetup = registerTestBed<TestSubject>(
|
||||||
doMountAsync: false,
|
(props: Props) => (
|
||||||
});
|
<PipelineProcessorsContextProvider {...props}>
|
||||||
|
<ProcessorsEditor /> <GlobalOnFailureProcessorsEditor />
|
||||||
|
</PipelineProcessorsContextProvider>
|
||||||
|
),
|
||||||
|
{
|
||||||
|
doMountAsync: false,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
export interface SetupResult extends TestBed<TestSubject> {
|
export interface SetupResult extends TestBed<TestSubject> {
|
||||||
actions: ReturnType<typeof createActions>;
|
actions: ReturnType<typeof createActions>;
|
||||||
|
@ -146,10 +158,6 @@ const createActions = (testBed: TestBed<TestSubject>) => {
|
||||||
find(`${processorSelector}.moreMenu.duplicateButton`).simulate('click');
|
find(`${processorSelector}.moreMenu.duplicateButton`).simulate('click');
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleOnFailure() {
|
|
||||||
find('pipelineEditorOnFailureToggle').simulate('click');
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -43,9 +43,9 @@ describe('Pipeline Editor', () => {
|
||||||
},
|
},
|
||||||
onFlyoutOpen: jest.fn(),
|
onFlyoutOpen: jest.fn(),
|
||||||
onUpdate,
|
onUpdate,
|
||||||
isTestButtonDisabled: false,
|
links: {
|
||||||
onTestPipelineClick: jest.fn(),
|
esDocsBasePath: 'test',
|
||||||
esDocsBasePath: 'test',
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -57,13 +57,6 @@ describe('Pipeline Editor', () => {
|
||||||
expect(arg.getData()).toEqual(testProcessors);
|
expect(arg.getData()).toEqual(testProcessors);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('toggles the on-failure processors tree', () => {
|
|
||||||
const { actions, exists } = testBed;
|
|
||||||
expect(exists('pipelineEditorOnFailureTree')).toBe(false);
|
|
||||||
actions.toggleOnFailure();
|
|
||||||
expect(exists('pipelineEditorOnFailureTree')).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('processors', () => {
|
describe('processors', () => {
|
||||||
it('adds a new processor', async () => {
|
it('adds a new processor', async () => {
|
||||||
const { actions } = testBed;
|
const { actions } = testBed;
|
||||||
|
@ -169,7 +162,6 @@ describe('Pipeline Editor', () => {
|
||||||
|
|
||||||
it('moves to and from the global on-failure tree', async () => {
|
it('moves to and from the global on-failure tree', async () => {
|
||||||
const { actions } = testBed;
|
const { actions } = testBed;
|
||||||
actions.toggleOnFailure();
|
|
||||||
await actions.addProcessor('onFailure', 'test', { if: '1 == 5' });
|
await actions.addProcessor('onFailure', 'test', { if: '1 == 5' });
|
||||||
actions.moveProcessor('processors>0', 'dropButtonBelow-onFailure>0');
|
actions.moveProcessor('processors>0', 'dropButtonBelow-onFailure>0');
|
||||||
const [onUpdateResult1] = onUpdate.mock.calls[onUpdate.mock.calls.length - 1];
|
const [onUpdateResult1] = onUpdate.mock.calls[onUpdate.mock.calls.length - 1];
|
||||||
|
|
|
@ -12,10 +12,10 @@ export {
|
||||||
|
|
||||||
export { ProcessorsTree, ProcessorInfo, OnActionHandler } from './processors_tree';
|
export { ProcessorsTree, ProcessorInfo, OnActionHandler } from './processors_tree';
|
||||||
|
|
||||||
export { PipelineProcessorsEditorItem } from './pipeline_processors_editor_item/pipeline_processors_editor_item';
|
export { PipelineProcessorsEditor } from './pipeline_processors_editor';
|
||||||
|
|
||||||
|
export { PipelineProcessorsEditorItem } from './pipeline_processors_editor_item';
|
||||||
|
|
||||||
export { ProcessorRemoveModal } from './processor_remove_modal';
|
export { ProcessorRemoveModal } from './processor_remove_modal';
|
||||||
|
|
||||||
export { ProcessorsTitleAndTestButton } from './processors_title_and_test_button';
|
export { OnDoneLoadJsonHandler, LoadFromJsonButton } from './load_from_json';
|
||||||
|
|
||||||
export { OnFailureProcessorsTitle } from './on_failure_processors_title';
|
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
/*
|
||||||
|
* 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 { i18n } from '@kbn/i18n';
|
||||||
|
import React, { FunctionComponent } from 'react';
|
||||||
|
import { EuiButton } from '@elastic/eui';
|
||||||
|
|
||||||
|
import { ModalProvider, OnDoneLoadJsonHandler } from './modal_provider';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
onDone: OnDoneLoadJsonHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
const i18nTexts = {
|
||||||
|
buttonLabel: i18n.translate('xpack.ingestPipelines.pipelineEditor.loadFromJson.buttonLabel', {
|
||||||
|
defaultMessage: 'Load JSON',
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const LoadFromJsonButton: FunctionComponent<Props> = ({ onDone }) => {
|
||||||
|
return (
|
||||||
|
<ModalProvider onDone={onDone}>
|
||||||
|
{(openModal) => {
|
||||||
|
return (
|
||||||
|
<EuiButton size="s" onClick={openModal}>
|
||||||
|
{i18nTexts.buttonLabel}
|
||||||
|
</EuiButton>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</ModalProvider>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,8 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export { LoadFromJsonButton } from './button';
|
||||||
|
export { OnDoneLoadJsonHandler } from './modal_provider';
|
|
@ -0,0 +1,127 @@
|
||||||
|
/*
|
||||||
|
* 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 from 'react';
|
||||||
|
import { ModalProvider, OnDoneLoadJsonHandler } from './modal_provider';
|
||||||
|
|
||||||
|
jest.mock('@elastic/eui', () => {
|
||||||
|
const original = jest.requireActual('@elastic/eui');
|
||||||
|
|
||||||
|
return {
|
||||||
|
...original,
|
||||||
|
// Mocking EuiCodeEditor, which uses React Ace under the hood
|
||||||
|
EuiCodeEditor: (props: any) => (
|
||||||
|
<input
|
||||||
|
data-test-subj="mockCodeEditor"
|
||||||
|
onChange={(syntheticEvent: any) => {
|
||||||
|
props.onChange(syntheticEvent.jsonString);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
jest.mock('lodash', () => {
|
||||||
|
const original = jest.requireActual('lodash');
|
||||||
|
|
||||||
|
return {
|
||||||
|
...original,
|
||||||
|
debounce: (fn: any) => fn,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
import { registerTestBed, TestBed } from '../../../../../../../../test_utils/testbed';
|
||||||
|
|
||||||
|
const setup = ({ onDone }: { onDone: OnDoneLoadJsonHandler }) => {
|
||||||
|
return registerTestBed(
|
||||||
|
() => (
|
||||||
|
<ModalProvider onDone={onDone}>
|
||||||
|
{(openModal) => {
|
||||||
|
return (
|
||||||
|
<button onClick={openModal} data-test-subj="button">
|
||||||
|
Load JSON
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</ModalProvider>
|
||||||
|
),
|
||||||
|
{
|
||||||
|
memoryRouter: {
|
||||||
|
wrapComponent: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)();
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('Load from JSON ModalProvider', () => {
|
||||||
|
let testBed: TestBed;
|
||||||
|
let onDone: jest.Mock;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
onDone = jest.fn();
|
||||||
|
testBed = await setup({ onDone });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('displays errors', () => {
|
||||||
|
const { find, exists } = testBed;
|
||||||
|
find('button').simulate('click');
|
||||||
|
expect(exists('loadJsonConfirmationModal'));
|
||||||
|
const invalidPipeline = '{}';
|
||||||
|
find('mockCodeEditor').simulate('change', { jsonString: invalidPipeline });
|
||||||
|
find('confirmModalConfirmButton').simulate('click');
|
||||||
|
const errorCallout = find('loadJsonConfirmationModal.errorCallOut');
|
||||||
|
expect(errorCallout.text()).toContain('Please ensure the JSON is a valid pipeline object.');
|
||||||
|
expect(onDone).toHaveBeenCalledTimes(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('passes through a valid pipeline object', () => {
|
||||||
|
const { find, exists } = testBed;
|
||||||
|
find('button').simulate('click');
|
||||||
|
expect(exists('loadJsonConfirmationModal'));
|
||||||
|
const validPipeline = JSON.stringify({
|
||||||
|
processors: [{ set: { field: 'test', value: 123 } }, { badType1: null }, { badType2: 1 }],
|
||||||
|
on_failure: [
|
||||||
|
{
|
||||||
|
gsub: {
|
||||||
|
field: '_index',
|
||||||
|
pattern: '(.monitoring-\\w+-)6(-.+)',
|
||||||
|
replacement: '$17$2',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
find('mockCodeEditor').simulate('change', { jsonString: validPipeline });
|
||||||
|
find('confirmModalConfirmButton').simulate('click');
|
||||||
|
expect(!exists('loadJsonConfirmationModal'));
|
||||||
|
expect(onDone).toHaveBeenCalledTimes(1);
|
||||||
|
expect(onDone.mock.calls[0][0]).toMatchInlineSnapshot(`
|
||||||
|
Object {
|
||||||
|
"on_failure": Array [
|
||||||
|
Object {
|
||||||
|
"gsub": Object {
|
||||||
|
"field": "_index",
|
||||||
|
"pattern": "(.monitoring-\\\\w+-)6(-.+)",
|
||||||
|
"replacement": "$17$2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"processors": Array [
|
||||||
|
Object {
|
||||||
|
"set": Object {
|
||||||
|
"field": "test",
|
||||||
|
"value": 123,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"badType1": null,
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"badType2": 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,138 @@
|
||||||
|
/*
|
||||||
|
* 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 { i18n } from '@kbn/i18n';
|
||||||
|
import { FormattedMessage } from '@kbn/i18n/react';
|
||||||
|
import React, { FunctionComponent, useRef, useState } from 'react';
|
||||||
|
import { EuiConfirmModal, EuiOverlayMask, EuiSpacer, EuiText, EuiCallOut } from '@elastic/eui';
|
||||||
|
|
||||||
|
import { JsonEditor, OnJsonEditorUpdateHandler } from '../../../../../shared_imports';
|
||||||
|
|
||||||
|
import { Processor } from '../../../../../../common/types';
|
||||||
|
|
||||||
|
import { deserialize } from '../../deserialize';
|
||||||
|
|
||||||
|
export type OnDoneLoadJsonHandler = (json: {
|
||||||
|
processors: Processor[];
|
||||||
|
on_failure?: Processor[];
|
||||||
|
}) => void;
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
onDone: OnDoneLoadJsonHandler;
|
||||||
|
children: (openModal: () => void) => React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
const i18nTexts = {
|
||||||
|
modalTitle: i18n.translate('xpack.ingestPipelines.pipelineEditor.loadFromJson.modalTitle', {
|
||||||
|
defaultMessage: 'Load JSON',
|
||||||
|
}),
|
||||||
|
buttons: {
|
||||||
|
cancel: i18n.translate('xpack.ingestPipelines.pipelineEditor.loadFromJson.buttons.cancel', {
|
||||||
|
defaultMessage: 'Cancel',
|
||||||
|
}),
|
||||||
|
confirm: i18n.translate('xpack.ingestPipelines.pipelineEditor.loadFromJson.buttons.confirm', {
|
||||||
|
defaultMessage: 'Load and overwrite',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
editor: {
|
||||||
|
label: i18n.translate('xpack.ingestPipelines.pipelineEditor.loadFromJson.editor', {
|
||||||
|
defaultMessage: 'Pipeline object',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
error: {
|
||||||
|
title: i18n.translate('xpack.ingestPipelines.pipelineEditor.loadFromJson.error.title', {
|
||||||
|
defaultMessage: 'Invalid pipeline',
|
||||||
|
}),
|
||||||
|
body: i18n.translate('xpack.ingestPipelines.pipelineEditor.loadFromJson.error.body', {
|
||||||
|
defaultMessage: 'Please ensure the JSON is a valid pipeline object.',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultValue = {};
|
||||||
|
const defaultValueRaw = JSON.stringify(defaultValue, null, 2);
|
||||||
|
|
||||||
|
export const ModalProvider: FunctionComponent<Props> = ({ onDone, children }) => {
|
||||||
|
const [isModalVisible, setIsModalVisible] = useState(false);
|
||||||
|
const [isValidJson, setIsValidJson] = useState(true);
|
||||||
|
const [error, setError] = useState<Error | undefined>();
|
||||||
|
const jsonContent = useRef<Parameters<OnJsonEditorUpdateHandler>['0']>({
|
||||||
|
isValid: true,
|
||||||
|
validate: () => true,
|
||||||
|
data: {
|
||||||
|
format: () => defaultValue,
|
||||||
|
raw: defaultValueRaw,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const onJsonUpdate: OnJsonEditorUpdateHandler = (jsonUpdateData) => {
|
||||||
|
setIsValidJson(jsonUpdateData.validate());
|
||||||
|
jsonContent.current = jsonUpdateData;
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{children(() => setIsModalVisible(true))}
|
||||||
|
{isModalVisible ? (
|
||||||
|
<EuiOverlayMask>
|
||||||
|
<EuiConfirmModal
|
||||||
|
data-test-subj="loadJsonConfirmationModal"
|
||||||
|
title={i18nTexts.modalTitle}
|
||||||
|
onCancel={() => {
|
||||||
|
setIsModalVisible(false);
|
||||||
|
}}
|
||||||
|
onConfirm={async () => {
|
||||||
|
try {
|
||||||
|
const json = jsonContent.current.data.format();
|
||||||
|
const { processors, on_failure: onFailure } = json;
|
||||||
|
// This function will throw if it cannot parse the pipeline object
|
||||||
|
deserialize({ processors, onFailure });
|
||||||
|
onDone(json as any);
|
||||||
|
setIsModalVisible(false);
|
||||||
|
} catch (e) {
|
||||||
|
setError(e);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
cancelButtonText={i18nTexts.buttons.cancel}
|
||||||
|
confirmButtonDisabled={!isValidJson}
|
||||||
|
confirmButtonText={i18nTexts.buttons.confirm}
|
||||||
|
maxWidth={600}
|
||||||
|
>
|
||||||
|
<div className="application">
|
||||||
|
<EuiText color="subdued">
|
||||||
|
<FormattedMessage
|
||||||
|
id="xpack.ingestPipelines.pipelineEditor.loadJsonModal.jsonEditorHelpText"
|
||||||
|
defaultMessage="Provide a pipeline object. This will override the existing pipeline processors and on-failure processors."
|
||||||
|
/>
|
||||||
|
</EuiText>
|
||||||
|
|
||||||
|
<EuiSpacer size="m" />
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<>
|
||||||
|
<EuiCallOut
|
||||||
|
data-test-subj="errorCallOut"
|
||||||
|
title={i18nTexts.error.title}
|
||||||
|
color="danger"
|
||||||
|
iconType="alert"
|
||||||
|
>
|
||||||
|
{i18nTexts.error.body}
|
||||||
|
</EuiCallOut>
|
||||||
|
<EuiSpacer size="m" />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<JsonEditor
|
||||||
|
label={i18nTexts.editor.label}
|
||||||
|
onUpdate={onJsonUpdate}
|
||||||
|
euiCodeEditorProps={{
|
||||||
|
height: '300px',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</EuiConfirmModal>
|
||||||
|
</EuiOverlayMask>
|
||||||
|
) : undefined}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,33 @@
|
||||||
|
/*
|
||||||
|
* 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, memo, useMemo } from 'react';
|
||||||
|
import { ProcessorsTree } from '.';
|
||||||
|
import { usePipelineProcessorsContext } from '../context';
|
||||||
|
|
||||||
|
import { ON_FAILURE_STATE_SCOPE, PROCESSOR_STATE_SCOPE } from '../processors_reducer';
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
stateSlice: typeof ON_FAILURE_STATE_SCOPE | typeof PROCESSOR_STATE_SCOPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PipelineProcessorsEditor: FunctionComponent<Props> = memo(
|
||||||
|
function PipelineProcessorsEditor({ stateSlice }) {
|
||||||
|
const {
|
||||||
|
onTreeAction,
|
||||||
|
state: { editor, processors },
|
||||||
|
} = usePipelineProcessorsContext();
|
||||||
|
const baseSelector = useMemo(() => [stateSlice], [stateSlice]);
|
||||||
|
return (
|
||||||
|
<ProcessorsTree
|
||||||
|
baseSelector={baseSelector}
|
||||||
|
processors={processors.state[stateSlice]}
|
||||||
|
onAction={onTreeAction}
|
||||||
|
movingProcessor={editor.mode.id === 'movingProcessor' ? editor.mode.arg : undefined}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
|
@ -9,7 +9,7 @@ import React, { FunctionComponent, useState } from 'react';
|
||||||
|
|
||||||
import { EuiContextMenuItem, EuiContextMenuPanel, EuiPopover, EuiButtonIcon } from '@elastic/eui';
|
import { EuiContextMenuItem, EuiContextMenuPanel, EuiPopover, EuiButtonIcon } from '@elastic/eui';
|
||||||
|
|
||||||
import { editorItemMessages } from './messages';
|
import { i18nTexts } from './i18n_texts';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
|
@ -39,7 +39,7 @@ export const ContextMenu: FunctionComponent<Props> = (props) => {
|
||||||
onDuplicate();
|
onDuplicate();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{editorItemMessages.duplicateButtonLabel}
|
{i18nTexts.duplicateButtonLabel}
|
||||||
</EuiContextMenuItem>,
|
</EuiContextMenuItem>,
|
||||||
showAddOnFailure ? (
|
showAddOnFailure ? (
|
||||||
<EuiContextMenuItem
|
<EuiContextMenuItem
|
||||||
|
@ -51,7 +51,7 @@ export const ContextMenu: FunctionComponent<Props> = (props) => {
|
||||||
onAddOnFailure();
|
onAddOnFailure();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{editorItemMessages.addOnFailureButtonLabel}
|
{i18nTexts.addOnFailureButtonLabel}
|
||||||
</EuiContextMenuItem>
|
</EuiContextMenuItem>
|
||||||
) : undefined,
|
) : undefined,
|
||||||
<EuiContextMenuItem
|
<EuiContextMenuItem
|
||||||
|
@ -64,7 +64,7 @@ export const ContextMenu: FunctionComponent<Props> = (props) => {
|
||||||
onDelete();
|
onDelete();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{editorItemMessages.deleteButtonLabel}
|
{i18nTexts.deleteButtonLabel}
|
||||||
</EuiContextMenuItem>,
|
</EuiContextMenuItem>,
|
||||||
].filter(Boolean) as JSX.Element[];
|
].filter(Boolean) as JSX.Element[];
|
||||||
|
|
||||||
|
@ -82,7 +82,7 @@ export const ContextMenu: FunctionComponent<Props> = (props) => {
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onClick={() => setIsOpen((v) => !v)}
|
onClick={() => setIsOpen((v) => !v)}
|
||||||
iconType="boxesHorizontal"
|
iconType="boxesHorizontal"
|
||||||
aria-label={editorItemMessages.moreButtonAriaLabel}
|
aria-label={i18nTexts.moreButtonAriaLabel}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
|
|
||||||
export const editorItemMessages = {
|
export const i18nTexts = {
|
||||||
moveButtonLabel: i18n.translate('xpack.ingestPipelines.pipelineEditor.item.moveButtonLabel', {
|
moveButtonLabel: i18n.translate('xpack.ingestPipelines.pipelineEditor.item.moveButtonLabel', {
|
||||||
defaultMessage: 'Move this processor',
|
defaultMessage: 'Move this processor',
|
||||||
}),
|
}),
|
|
@ -25,7 +25,7 @@ import './pipeline_processors_editor_item.scss';
|
||||||
|
|
||||||
import { InlineTextInput } from './inline_text_input';
|
import { InlineTextInput } from './inline_text_input';
|
||||||
import { ContextMenu } from './context_menu';
|
import { ContextMenu } from './context_menu';
|
||||||
import { editorItemMessages } from './messages';
|
import { i18nTexts } from './i18n_texts';
|
||||||
import { ProcessorInfo } from '../processors_tree';
|
import { ProcessorInfo } from '../processors_tree';
|
||||||
|
|
||||||
export interface Handlers {
|
export interface Handlers {
|
||||||
|
@ -52,7 +52,7 @@ export const PipelineProcessorsEditorItem: FunctionComponent<Props> = memo(
|
||||||
renderOnFailureHandlers,
|
renderOnFailureHandlers,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
state: { editor, processorsDispatch },
|
state: { editor, processors },
|
||||||
} = usePipelineProcessorsContext();
|
} = usePipelineProcessorsContext();
|
||||||
|
|
||||||
const isDisabled = editor.mode.id !== 'idle';
|
const isDisabled = editor.mode.id !== 'idle';
|
||||||
|
@ -115,7 +115,7 @@ export const PipelineProcessorsEditorItem: FunctionComponent<Props> = memo(
|
||||||
description: nextDescription,
|
description: nextDescription,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
processorsDispatch({
|
processors.dispatch({
|
||||||
type: 'updateProcessor',
|
type: 'updateProcessor',
|
||||||
payload: {
|
payload: {
|
||||||
processor: {
|
processor: {
|
||||||
|
@ -126,17 +126,17 @@ export const PipelineProcessorsEditorItem: FunctionComponent<Props> = memo(
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
ariaLabel={editorItemMessages.processorTypeLabel({ type: processor.type })}
|
ariaLabel={i18nTexts.processorTypeLabel({ type: processor.type })}
|
||||||
text={description}
|
text={description}
|
||||||
placeholder={editorItemMessages.descriptionPlaceholder}
|
placeholder={i18nTexts.descriptionPlaceholder}
|
||||||
/>
|
/>
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
<EuiFlexItem className={actionElementClasses} grow={false}>
|
<EuiFlexItem className={actionElementClasses} grow={false}>
|
||||||
{!isInMoveMode && (
|
{!isInMoveMode && (
|
||||||
<EuiToolTip content={editorItemMessages.editButtonLabel}>
|
<EuiToolTip content={i18nTexts.editButtonLabel}>
|
||||||
<EuiButtonIcon
|
<EuiButtonIcon
|
||||||
disabled={isDisabled}
|
disabled={isDisabled}
|
||||||
aria-label={editorItemMessages.editButtonLabel}
|
aria-label={i18nTexts.editButtonLabel}
|
||||||
iconType="pencil"
|
iconType="pencil"
|
||||||
size="s"
|
size="s"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
@ -151,12 +151,12 @@ export const PipelineProcessorsEditorItem: FunctionComponent<Props> = memo(
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
<EuiFlexItem className={actionElementClasses} grow={false}>
|
<EuiFlexItem className={actionElementClasses} grow={false}>
|
||||||
{!isInMoveMode && (
|
{!isInMoveMode && (
|
||||||
<EuiToolTip content={editorItemMessages.moveButtonLabel}>
|
<EuiToolTip content={i18nTexts.moveButtonLabel}>
|
||||||
<EuiButtonIcon
|
<EuiButtonIcon
|
||||||
data-test-subj="moveItemButton"
|
data-test-subj="moveItemButton"
|
||||||
size="s"
|
size="s"
|
||||||
disabled={isDisabled}
|
disabled={isDisabled}
|
||||||
aria-label={editorItemMessages.moveButtonLabel}
|
aria-label={i18nTexts.moveButtonLabel}
|
||||||
onClick={onMove}
|
onClick={onMove}
|
||||||
iconType="sortable"
|
iconType="sortable"
|
||||||
/>
|
/>
|
||||||
|
@ -165,7 +165,7 @@ export const PipelineProcessorsEditorItem: FunctionComponent<Props> = memo(
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
<EuiFlexItem grow={false} className={cancelMoveButtonClasses}>
|
<EuiFlexItem grow={false} className={cancelMoveButtonClasses}>
|
||||||
<EuiButton data-test-subj="cancelMoveItemButton" size="s" onClick={onCancelMove}>
|
<EuiButton data-test-subj="cancelMoveItemButton" size="s" onClick={onCancelMove}>
|
||||||
{editorItemMessages.cancelMoveButtonLabel}
|
{i18nTexts.cancelMoveButtonLabel}
|
||||||
</EuiButton>
|
</EuiButton>
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
</EuiFlexGroup>
|
</EuiFlexGroup>
|
||||||
|
@ -183,7 +183,7 @@ export const PipelineProcessorsEditorItem: FunctionComponent<Props> = memo(
|
||||||
editor.setMode({ id: 'removingProcessor', arg: { selector } });
|
editor.setMode({ id: 'removingProcessor', arg: { selector } });
|
||||||
}}
|
}}
|
||||||
onDuplicate={() => {
|
onDuplicate={() => {
|
||||||
processorsDispatch({
|
processors.dispatch({
|
||||||
type: 'duplicateProcessor',
|
type: 'duplicateProcessor',
|
||||||
payload: {
|
payload: {
|
||||||
source: selector,
|
source: selector,
|
||||||
|
|
|
@ -9,11 +9,13 @@ import { FormattedMessage } from '@kbn/i18n/react';
|
||||||
import React, { FunctionComponent, memo, useEffect } from 'react';
|
import React, { FunctionComponent, memo, useEffect } from 'react';
|
||||||
import {
|
import {
|
||||||
EuiButton,
|
EuiButton,
|
||||||
|
EuiButtonEmpty,
|
||||||
EuiHorizontalRule,
|
EuiHorizontalRule,
|
||||||
EuiFlyout,
|
EuiFlyout,
|
||||||
EuiFlyoutHeader,
|
EuiFlyoutHeader,
|
||||||
EuiTitle,
|
|
||||||
EuiFlyoutBody,
|
EuiFlyoutBody,
|
||||||
|
EuiFlyoutFooter,
|
||||||
|
EuiTitle,
|
||||||
EuiFlexGroup,
|
EuiFlexGroup,
|
||||||
EuiFlexItem,
|
EuiFlexItem,
|
||||||
} from '@elastic/eui';
|
} from '@elastic/eui';
|
||||||
|
@ -44,6 +46,11 @@ const addButtonLabel = i18n.translate(
|
||||||
{ defaultMessage: 'Add' }
|
{ defaultMessage: 'Add' }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const cancelButtonLabel = i18n.translate(
|
||||||
|
'xpack.ingestPipelines.settingsFormOnFailureFlyout.cancelButtonLabel',
|
||||||
|
{ defaultMessage: 'Cancel' }
|
||||||
|
);
|
||||||
|
|
||||||
export const ProcessorSettingsForm: FunctionComponent<Props> = memo(
|
export const ProcessorSettingsForm: FunctionComponent<Props> = memo(
|
||||||
({ processor, form, isOnFailure, onClose, onOpen }) => {
|
({ processor, form, isOnFailure, onClose, onOpen }) => {
|
||||||
const {
|
const {
|
||||||
|
@ -71,7 +78,7 @@ export const ProcessorSettingsForm: FunctionComponent<Props> = memo(
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form data-test-subj="processorSettingsForm" form={form}>
|
<Form data-test-subj="processorSettingsForm" form={form}>
|
||||||
<EuiFlyout onClose={onClose}>
|
<EuiFlyout size="m" maxWidth={720} onClose={onClose}>
|
||||||
<EuiFlyoutHeader>
|
<EuiFlyoutHeader>
|
||||||
<EuiFlexGroup gutterSize="xs">
|
<EuiFlexGroup gutterSize="xs">
|
||||||
<EuiFlexItem>
|
<EuiFlexItem>
|
||||||
|
@ -109,30 +116,19 @@ export const ProcessorSettingsForm: FunctionComponent<Props> = memo(
|
||||||
<FormDataProvider pathsToWatch="type">
|
<FormDataProvider pathsToWatch="type">
|
||||||
{(arg: any) => {
|
{(arg: any) => {
|
||||||
const { type } = arg;
|
const { type } = arg;
|
||||||
let formContent: React.ReactNode | undefined;
|
|
||||||
|
|
||||||
if (type?.length) {
|
if (type?.length) {
|
||||||
const formDescriptor = getProcessorFormDescriptor(type as any);
|
const formDescriptor = getProcessorFormDescriptor(type as any);
|
||||||
|
|
||||||
if (formDescriptor?.FieldsComponent) {
|
if (formDescriptor?.FieldsComponent) {
|
||||||
formContent = (
|
return (
|
||||||
<>
|
<>
|
||||||
<formDescriptor.FieldsComponent />
|
<formDescriptor.FieldsComponent />
|
||||||
<CommonProcessorFields />
|
<CommonProcessorFields />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
formContent = <Custom defaultOptions={processor?.options} />;
|
|
||||||
}
|
}
|
||||||
|
return <Custom defaultOptions={processor?.options} />;
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{formContent}
|
|
||||||
<EuiButton data-test-subj="submitButton" onClick={form.submit}>
|
|
||||||
{processor ? updateButtonLabel : addButtonLabel}
|
|
||||||
</EuiButton>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the user has not yet defined a type, we do not show any settings fields
|
// If the user has not yet defined a type, we do not show any settings fields
|
||||||
|
@ -140,6 +136,24 @@ export const ProcessorSettingsForm: FunctionComponent<Props> = memo(
|
||||||
}}
|
}}
|
||||||
</FormDataProvider>
|
</FormDataProvider>
|
||||||
</EuiFlyoutBody>
|
</EuiFlyoutBody>
|
||||||
|
<EuiFlyoutFooter>
|
||||||
|
<EuiFlexGroup justifyContent="flexEnd">
|
||||||
|
<EuiFlexItem grow={false}>
|
||||||
|
<EuiButtonEmpty onClick={onClose}>{cancelButtonLabel}</EuiButtonEmpty>
|
||||||
|
</EuiFlexItem>
|
||||||
|
<EuiFlexItem grow={false}>
|
||||||
|
<EuiButton
|
||||||
|
fill
|
||||||
|
data-test-subj="submitButton"
|
||||||
|
onClick={() => {
|
||||||
|
form.submit();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{processor ? updateButtonLabel : addButtonLabel}
|
||||||
|
</EuiButton>
|
||||||
|
</EuiFlexItem>
|
||||||
|
</EuiFlexGroup>
|
||||||
|
</EuiFlyoutFooter>
|
||||||
</EuiFlyout>
|
</EuiFlyout>
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
|
|
|
@ -4,7 +4,12 @@
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { ON_FAILURE_STATE_SCOPE, PROCESSOR_STATE_SCOPE } from './processors_reducer';
|
||||||
|
|
||||||
export enum DropSpecialLocations {
|
export enum DropSpecialLocations {
|
||||||
top = 'TOP',
|
top = 'TOP',
|
||||||
bottom = 'BOTTOM',
|
bottom = 'BOTTOM',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const PROCESSORS_BASE_SELECTOR = [PROCESSOR_STATE_SCOPE];
|
||||||
|
export const ON_FAILURE_BASE_SELECTOR = [ON_FAILURE_STATE_SCOPE];
|
||||||
|
|
|
@ -4,41 +4,242 @@
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { createContext, Dispatch, FunctionComponent, useContext, useState } from 'react';
|
import React, {
|
||||||
import { EditorMode } from './types';
|
createContext,
|
||||||
import { ProcessorsDispatch } from './processors_reducer';
|
Dispatch,
|
||||||
|
FunctionComponent,
|
||||||
|
useCallback,
|
||||||
|
useContext,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useState,
|
||||||
|
useRef,
|
||||||
|
} from 'react';
|
||||||
|
|
||||||
|
import { Processor } from '../../../../common/types';
|
||||||
|
|
||||||
|
import { EditorMode, FormValidityState, OnFormUpdateArg, OnUpdateHandlerArg } from './types';
|
||||||
|
|
||||||
|
import {
|
||||||
|
ProcessorsDispatch,
|
||||||
|
useProcessorsState,
|
||||||
|
State as ProcessorsState,
|
||||||
|
isOnFailureSelector,
|
||||||
|
} from './processors_reducer';
|
||||||
|
|
||||||
|
import { deserialize } from './deserialize';
|
||||||
|
|
||||||
|
import { serialize } from './serialize';
|
||||||
|
|
||||||
|
import { OnSubmitHandler, ProcessorSettingsForm } from './components/processor_settings_form';
|
||||||
|
|
||||||
|
import { OnActionHandler } from './components/processors_tree';
|
||||||
|
|
||||||
|
import { ProcessorRemoveModal } from './components';
|
||||||
|
|
||||||
|
import { getValue } from './utils';
|
||||||
|
|
||||||
interface Links {
|
interface Links {
|
||||||
esDocsBasePath: string;
|
esDocsBasePath: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PipelineProcessorsContext = createContext<{
|
interface ContextValue {
|
||||||
links: Links;
|
links: Links;
|
||||||
|
onTreeAction: OnActionHandler;
|
||||||
state: {
|
state: {
|
||||||
processorsDispatch: ProcessorsDispatch;
|
processors: {
|
||||||
|
state: ProcessorsState;
|
||||||
|
dispatch: ProcessorsDispatch;
|
||||||
|
};
|
||||||
editor: {
|
editor: {
|
||||||
mode: EditorMode;
|
mode: EditorMode;
|
||||||
setMode: Dispatch<EditorMode>;
|
setMode: Dispatch<EditorMode>;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}>({} as any);
|
}
|
||||||
|
|
||||||
interface Props {
|
const PipelineProcessorsContext = createContext<ContextValue>({} as any);
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
links: Links;
|
links: Links;
|
||||||
processorsDispatch: ProcessorsDispatch;
|
value: {
|
||||||
|
processors: Processor[];
|
||||||
|
onFailure?: Processor[];
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Give users a way to react to this component opening a flyout
|
||||||
|
*/
|
||||||
|
onFlyoutOpen: () => void;
|
||||||
|
onUpdate: (arg: OnUpdateHandlerArg) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PipelineProcessorsContextProvider: FunctionComponent<Props> = ({
|
export const PipelineProcessorsContextProvider: FunctionComponent<Props> = ({
|
||||||
links,
|
links,
|
||||||
|
value: { processors: originalProcessors, onFailure: originalOnFailureProcessors },
|
||||||
|
onUpdate,
|
||||||
|
onFlyoutOpen,
|
||||||
children,
|
children,
|
||||||
processorsDispatch,
|
|
||||||
}) => {
|
}) => {
|
||||||
|
const initRef = useRef(false);
|
||||||
const [mode, setMode] = useState<EditorMode>({ id: 'idle' });
|
const [mode, setMode] = useState<EditorMode>({ id: 'idle' });
|
||||||
|
const deserializedResult = useMemo(
|
||||||
|
() =>
|
||||||
|
deserialize({
|
||||||
|
processors: originalProcessors,
|
||||||
|
onFailure: originalOnFailureProcessors,
|
||||||
|
}),
|
||||||
|
[originalProcessors, originalOnFailureProcessors]
|
||||||
|
);
|
||||||
|
const [processorsState, processorsDispatch] = useProcessorsState(deserializedResult);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (initRef.current) {
|
||||||
|
processorsDispatch({
|
||||||
|
type: 'loadProcessors',
|
||||||
|
payload: {
|
||||||
|
newState: deserializedResult,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
initRef.current = true;
|
||||||
|
}
|
||||||
|
}, [deserializedResult, processorsDispatch]);
|
||||||
|
|
||||||
|
const { onFailure: onFailureProcessors, processors } = processorsState;
|
||||||
|
|
||||||
|
const [formState, setFormState] = useState<FormValidityState>({
|
||||||
|
validate: () => Promise.resolve(true),
|
||||||
|
});
|
||||||
|
|
||||||
|
const onFormUpdate = useCallback<(arg: OnFormUpdateArg<any>) => void>(
|
||||||
|
({ isValid, validate }) => {
|
||||||
|
setFormState({
|
||||||
|
validate: async () => {
|
||||||
|
if (isValid === undefined) {
|
||||||
|
return validate();
|
||||||
|
}
|
||||||
|
return isValid;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[setFormState]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
onUpdate({
|
||||||
|
validate: async () => {
|
||||||
|
const formValid = await formState.validate();
|
||||||
|
return formValid && mode.id === 'idle';
|
||||||
|
},
|
||||||
|
getData: () =>
|
||||||
|
serialize({
|
||||||
|
onFailure: onFailureProcessors,
|
||||||
|
processors,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}, [processors, onFailureProcessors, onUpdate, formState, mode]);
|
||||||
|
|
||||||
|
const onSubmit = useCallback<OnSubmitHandler>(
|
||||||
|
(processorTypeAndOptions) => {
|
||||||
|
switch (mode.id) {
|
||||||
|
case 'creatingProcessor':
|
||||||
|
processorsDispatch({
|
||||||
|
type: 'addProcessor',
|
||||||
|
payload: {
|
||||||
|
processor: { ...processorTypeAndOptions },
|
||||||
|
targetSelector: mode.arg.selector,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'editingProcessor':
|
||||||
|
processorsDispatch({
|
||||||
|
type: 'updateProcessor',
|
||||||
|
payload: {
|
||||||
|
processor: {
|
||||||
|
...mode.arg.processor,
|
||||||
|
...processorTypeAndOptions,
|
||||||
|
},
|
||||||
|
selector: mode.arg.selector,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
setMode({ id: 'idle' });
|
||||||
|
},
|
||||||
|
[processorsDispatch, mode, setMode]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onCloseSettingsForm = useCallback(() => {
|
||||||
|
setMode({ id: 'idle' });
|
||||||
|
setFormState({ validate: () => Promise.resolve(true) });
|
||||||
|
}, [setFormState, setMode]);
|
||||||
|
|
||||||
|
const onTreeAction = useCallback<OnActionHandler>(
|
||||||
|
(action) => {
|
||||||
|
switch (action.type) {
|
||||||
|
case 'addProcessor':
|
||||||
|
setMode({ id: 'creatingProcessor', arg: { selector: action.payload.target } });
|
||||||
|
break;
|
||||||
|
case 'move':
|
||||||
|
setMode({ id: 'idle' });
|
||||||
|
processorsDispatch({
|
||||||
|
type: 'moveProcessor',
|
||||||
|
payload: action.payload,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'selectToMove':
|
||||||
|
setMode({ id: 'movingProcessor', arg: action.payload.info });
|
||||||
|
break;
|
||||||
|
case 'cancelMove':
|
||||||
|
setMode({ id: 'idle' });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[processorsDispatch, setMode]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PipelineProcessorsContext.Provider
|
<PipelineProcessorsContext.Provider
|
||||||
value={{ links, state: { editor: { mode, setMode }, processorsDispatch } }}
|
value={{
|
||||||
|
links,
|
||||||
|
onTreeAction,
|
||||||
|
state: {
|
||||||
|
editor: { mode, setMode },
|
||||||
|
processors: { state: processorsState, dispatch: processorsDispatch },
|
||||||
|
},
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|
||||||
|
{mode.id === 'editingProcessor' || mode.id === 'creatingProcessor' ? (
|
||||||
|
<ProcessorSettingsForm
|
||||||
|
isOnFailure={isOnFailureSelector(mode.arg.selector)}
|
||||||
|
processor={mode.id === 'editingProcessor' ? mode.arg.processor : undefined}
|
||||||
|
onOpen={onFlyoutOpen}
|
||||||
|
onFormUpdate={onFormUpdate}
|
||||||
|
onSubmit={onSubmit}
|
||||||
|
onClose={onCloseSettingsForm}
|
||||||
|
/>
|
||||||
|
) : undefined}
|
||||||
|
{mode.id === 'removingProcessor' && (
|
||||||
|
<ProcessorRemoveModal
|
||||||
|
selector={mode.arg.selector}
|
||||||
|
processor={getValue(mode.arg.selector, {
|
||||||
|
processors,
|
||||||
|
onFailure: onFailureProcessors,
|
||||||
|
})}
|
||||||
|
onResult={({ confirmed, selector }) => {
|
||||||
|
if (confirmed) {
|
||||||
|
processorsDispatch({
|
||||||
|
type: 'removeProcessor',
|
||||||
|
payload: { selector },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setMode({ id: 'idle' });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</PipelineProcessorsContext.Provider>
|
</PipelineProcessorsContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
/*
|
||||||
|
* 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 { deserialize } from './deserialize';
|
||||||
|
|
||||||
|
describe('deserialize', () => {
|
||||||
|
it('tolerates certain bad values correctly', () => {
|
||||||
|
expect(
|
||||||
|
deserialize({
|
||||||
|
processors: [
|
||||||
|
{ set: { field: 'test', value: 123 } },
|
||||||
|
{ badType1: null } as any,
|
||||||
|
{ badType2: 1 } as any,
|
||||||
|
],
|
||||||
|
onFailure: [
|
||||||
|
{
|
||||||
|
gsub: {
|
||||||
|
field: '_index',
|
||||||
|
pattern: '(.monitoring-\\w+-)6(-.+)',
|
||||||
|
replacement: '$17$2',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
).toEqual({
|
||||||
|
processors: [
|
||||||
|
{
|
||||||
|
id: expect.any(String),
|
||||||
|
type: 'set',
|
||||||
|
options: {
|
||||||
|
field: 'test',
|
||||||
|
value: 123,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: expect.any(String),
|
||||||
|
onFailure: undefined,
|
||||||
|
type: 'badType1',
|
||||||
|
options: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: expect.any(String),
|
||||||
|
onFailure: undefined,
|
||||||
|
type: 'badType2',
|
||||||
|
options: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
onFailure: [
|
||||||
|
{
|
||||||
|
id: expect.any(String),
|
||||||
|
type: 'gsub',
|
||||||
|
onFailure: undefined,
|
||||||
|
options: {
|
||||||
|
field: '_index',
|
||||||
|
pattern: '(.monitoring-\\w+-)6(-.+)',
|
||||||
|
replacement: '$17$2',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws for unacceptable values', () => {
|
||||||
|
expect(() => {
|
||||||
|
deserialize({
|
||||||
|
processors: [{ reallyBad: undefined } as any, 1 as any],
|
||||||
|
onFailure: [],
|
||||||
|
});
|
||||||
|
}).toThrow('Invalid processor type');
|
||||||
|
});
|
||||||
|
});
|
|
@ -22,12 +22,16 @@ const getProcessorType = (processor: Processor): string => {
|
||||||
* See the definition of {@link ProcessorInternal} for why this works to extract the
|
* See the definition of {@link ProcessorInternal} for why this works to extract the
|
||||||
* processor type.
|
* processor type.
|
||||||
*/
|
*/
|
||||||
return Object.keys(processor)[0]!;
|
const type: unknown = Object.keys(processor)[0];
|
||||||
|
if (typeof type !== 'string') {
|
||||||
|
throw new Error(`Invalid processor type. Received "${type}"`);
|
||||||
|
}
|
||||||
|
return type;
|
||||||
};
|
};
|
||||||
|
|
||||||
const convertToPipelineInternalProcessor = (processor: Processor): ProcessorInternal => {
|
const convertToPipelineInternalProcessor = (processor: Processor): ProcessorInternal => {
|
||||||
const type = getProcessorType(processor);
|
const type = getProcessorType(processor);
|
||||||
const { on_failure: originalOnFailure, ...options } = processor[type];
|
const { on_failure: originalOnFailure, ...options } = processor[type] ?? {};
|
||||||
const onFailure = originalOnFailure?.length
|
const onFailure = originalOnFailure?.length
|
||||||
? convertProcessors(originalOnFailure)
|
? convertProcessors(originalOnFailure)
|
||||||
: (originalOnFailure as ProcessorInternal[] | undefined);
|
: (originalOnFailure as ProcessorInternal[] | undefined);
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
/*
|
||||||
|
* 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 { PipelineProcessorsEditor } from '../components';
|
||||||
|
|
||||||
|
export const GlobalOnFailureProcessorsEditor: FunctionComponent = () => {
|
||||||
|
return <PipelineProcessorsEditor stateSlice="onFailure" />;
|
||||||
|
};
|
|
@ -0,0 +1,8 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export { GlobalOnFailureProcessorsEditor } from './global_on_failure_processors_editor';
|
||||||
|
export { ProcessorsEditor } from './processors_editor';
|
|
@ -0,0 +1,13 @@
|
||||||
|
/*
|
||||||
|
* 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 { PipelineProcessorsEditor } from '../components';
|
||||||
|
|
||||||
|
export const ProcessorsEditor: FunctionComponent = () => {
|
||||||
|
return <PipelineProcessorsEditor stateSlice="processors" />;
|
||||||
|
};
|
|
@ -4,8 +4,12 @@
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export { PipelineProcessorsEditor, OnUpdateHandler } from './pipeline_processors_editor.container';
|
export { PipelineProcessorsContextProvider, Props } from './context';
|
||||||
|
|
||||||
export { OnUpdateHandlerArg } from './types';
|
export { ProcessorsEditor, GlobalOnFailureProcessorsEditor } from './editors';
|
||||||
|
|
||||||
|
export { OnUpdateHandlerArg, OnUpdateHandler } from './types';
|
||||||
|
|
||||||
export { SerializeResult } from './serialize';
|
export { SerializeResult } from './serialize';
|
||||||
|
|
||||||
|
export { LoadFromJsonButton, OnDoneLoadJsonHandler } from './components';
|
||||||
|
|
|
@ -1,74 +0,0 @@
|
||||||
/*
|
|
||||||
* 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, useMemo } from 'react';
|
|
||||||
|
|
||||||
import { Processor } from '../../../../common/types';
|
|
||||||
|
|
||||||
import { deserialize } from './deserialize';
|
|
||||||
|
|
||||||
import { useProcessorsState } from './processors_reducer';
|
|
||||||
|
|
||||||
import { PipelineProcessorsContextProvider } from './context';
|
|
||||||
|
|
||||||
import { OnUpdateHandlerArg } from './types';
|
|
||||||
|
|
||||||
import { PipelineProcessorsEditor as PipelineProcessorsEditorUI } from './pipeline_processors_editor';
|
|
||||||
|
|
||||||
export interface Props {
|
|
||||||
value: {
|
|
||||||
processors: Processor[];
|
|
||||||
onFailure?: Processor[];
|
|
||||||
};
|
|
||||||
onUpdate: (arg: OnUpdateHandlerArg) => void;
|
|
||||||
isTestButtonDisabled: boolean;
|
|
||||||
onTestPipelineClick: () => void;
|
|
||||||
esDocsBasePath: string;
|
|
||||||
/**
|
|
||||||
* Give users a way to react to this component opening a flyout
|
|
||||||
*/
|
|
||||||
onFlyoutOpen: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type OnUpdateHandler = (arg: OnUpdateHandlerArg) => void;
|
|
||||||
|
|
||||||
export const PipelineProcessorsEditor: FunctionComponent<Props> = ({
|
|
||||||
value: { processors: originalProcessors, onFailure: originalOnFailureProcessors },
|
|
||||||
onFlyoutOpen,
|
|
||||||
onUpdate,
|
|
||||||
isTestButtonDisabled,
|
|
||||||
esDocsBasePath,
|
|
||||||
onTestPipelineClick,
|
|
||||||
}) => {
|
|
||||||
const deserializedResult = useMemo(
|
|
||||||
() =>
|
|
||||||
deserialize({
|
|
||||||
processors: originalProcessors,
|
|
||||||
onFailure: originalOnFailureProcessors,
|
|
||||||
}),
|
|
||||||
// TODO: Re-add the dependency on the props and make the state set-able
|
|
||||||
// when new props come in so that this component will be controllable
|
|
||||||
[] // eslint-disable-line react-hooks/exhaustive-deps
|
|
||||||
);
|
|
||||||
const [processorsState, processorsDispatch] = useProcessorsState(deserializedResult);
|
|
||||||
const { processors, onFailure } = processorsState;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<PipelineProcessorsContextProvider
|
|
||||||
processorsDispatch={processorsDispatch}
|
|
||||||
links={{ esDocsBasePath }}
|
|
||||||
>
|
|
||||||
<PipelineProcessorsEditorUI
|
|
||||||
onFlyoutOpen={onFlyoutOpen}
|
|
||||||
onUpdate={onUpdate}
|
|
||||||
processors={processors}
|
|
||||||
onFailureProcessors={onFailure}
|
|
||||||
isTestButtonDisabled={isTestButtonDisabled}
|
|
||||||
onTestPipelineClick={onTestPipelineClick}
|
|
||||||
/>
|
|
||||||
</PipelineProcessorsContextProvider>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,239 +0,0 @@
|
||||||
/*
|
|
||||||
* 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 { FormattedMessage } from '@kbn/i18n/react';
|
|
||||||
import React, { FunctionComponent, useCallback, memo, useState, useEffect } from 'react';
|
|
||||||
import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiSwitch } from '@elastic/eui';
|
|
||||||
|
|
||||||
import './pipeline_processors_editor.scss';
|
|
||||||
|
|
||||||
import {
|
|
||||||
ProcessorsTitleAndTestButton,
|
|
||||||
OnFailureProcessorsTitle,
|
|
||||||
ProcessorsTree,
|
|
||||||
ProcessorRemoveModal,
|
|
||||||
OnActionHandler,
|
|
||||||
OnSubmitHandler,
|
|
||||||
ProcessorSettingsForm,
|
|
||||||
} from './components';
|
|
||||||
|
|
||||||
import { ProcessorInternal, OnUpdateHandlerArg, FormValidityState, OnFormUpdateArg } from './types';
|
|
||||||
|
|
||||||
import {
|
|
||||||
ON_FAILURE_STATE_SCOPE,
|
|
||||||
PROCESSOR_STATE_SCOPE,
|
|
||||||
isOnFailureSelector,
|
|
||||||
} from './processors_reducer';
|
|
||||||
|
|
||||||
const PROCESSORS_BASE_SELECTOR = [PROCESSOR_STATE_SCOPE];
|
|
||||||
const ON_FAILURE_BASE_SELECTOR = [ON_FAILURE_STATE_SCOPE];
|
|
||||||
|
|
||||||
import { serialize } from './serialize';
|
|
||||||
import { getValue } from './utils';
|
|
||||||
import { usePipelineProcessorsContext } from './context';
|
|
||||||
|
|
||||||
export interface Props {
|
|
||||||
processors: ProcessorInternal[];
|
|
||||||
onFailureProcessors: ProcessorInternal[];
|
|
||||||
onUpdate: (arg: OnUpdateHandlerArg) => void;
|
|
||||||
isTestButtonDisabled: boolean;
|
|
||||||
onTestPipelineClick: () => void;
|
|
||||||
onFlyoutOpen: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const PipelineProcessorsEditor: FunctionComponent<Props> = memo(
|
|
||||||
function PipelineProcessorsEditor({
|
|
||||||
processors,
|
|
||||||
onFailureProcessors,
|
|
||||||
onTestPipelineClick,
|
|
||||||
isTestButtonDisabled,
|
|
||||||
onUpdate,
|
|
||||||
onFlyoutOpen,
|
|
||||||
}) {
|
|
||||||
const {
|
|
||||||
state: { editor, processorsDispatch },
|
|
||||||
} = usePipelineProcessorsContext();
|
|
||||||
|
|
||||||
const { mode: editorMode, setMode: setEditorMode } = editor;
|
|
||||||
|
|
||||||
const [formState, setFormState] = useState<FormValidityState>({
|
|
||||||
validate: () => Promise.resolve(true),
|
|
||||||
});
|
|
||||||
|
|
||||||
const onFormUpdate = useCallback<(arg: OnFormUpdateArg<any>) => void>(
|
|
||||||
({ isValid, validate }) => {
|
|
||||||
setFormState({
|
|
||||||
validate: async () => {
|
|
||||||
if (isValid === undefined) {
|
|
||||||
return validate();
|
|
||||||
}
|
|
||||||
return isValid;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[setFormState]
|
|
||||||
);
|
|
||||||
|
|
||||||
const [showGlobalOnFailure, setShowGlobalOnFailure] = useState<boolean>(
|
|
||||||
Boolean(onFailureProcessors.length)
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
onUpdate({
|
|
||||||
validate: async () => {
|
|
||||||
const formValid = await formState.validate();
|
|
||||||
return formValid && editorMode.id === 'idle';
|
|
||||||
},
|
|
||||||
getData: () =>
|
|
||||||
serialize({
|
|
||||||
onFailure: showGlobalOnFailure ? onFailureProcessors : undefined,
|
|
||||||
processors,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
}, [processors, onFailureProcessors, onUpdate, formState, editorMode, showGlobalOnFailure]);
|
|
||||||
|
|
||||||
const onSubmit = useCallback<OnSubmitHandler>(
|
|
||||||
(processorTypeAndOptions) => {
|
|
||||||
switch (editorMode.id) {
|
|
||||||
case 'creatingProcessor':
|
|
||||||
processorsDispatch({
|
|
||||||
type: 'addProcessor',
|
|
||||||
payload: {
|
|
||||||
processor: { ...processorTypeAndOptions },
|
|
||||||
targetSelector: editorMode.arg.selector,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case 'editingProcessor':
|
|
||||||
processorsDispatch({
|
|
||||||
type: 'updateProcessor',
|
|
||||||
payload: {
|
|
||||||
processor: {
|
|
||||||
...editorMode.arg.processor,
|
|
||||||
...processorTypeAndOptions,
|
|
||||||
},
|
|
||||||
selector: editorMode.arg.selector,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
setEditorMode({ id: 'idle' });
|
|
||||||
},
|
|
||||||
[processorsDispatch, editorMode, setEditorMode]
|
|
||||||
);
|
|
||||||
|
|
||||||
const onCloseSettingsForm = useCallback(() => {
|
|
||||||
setEditorMode({ id: 'idle' });
|
|
||||||
setFormState({ validate: () => Promise.resolve(true) });
|
|
||||||
}, [setFormState, setEditorMode]);
|
|
||||||
|
|
||||||
const onTreeAction = useCallback<OnActionHandler>(
|
|
||||||
(action) => {
|
|
||||||
switch (action.type) {
|
|
||||||
case 'addProcessor':
|
|
||||||
setEditorMode({ id: 'creatingProcessor', arg: { selector: action.payload.target } });
|
|
||||||
break;
|
|
||||||
case 'move':
|
|
||||||
setEditorMode({ id: 'idle' });
|
|
||||||
processorsDispatch({
|
|
||||||
type: 'moveProcessor',
|
|
||||||
payload: action.payload,
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case 'selectToMove':
|
|
||||||
setEditorMode({ id: 'movingProcessor', arg: action.payload.info });
|
|
||||||
break;
|
|
||||||
case 'cancelMove':
|
|
||||||
setEditorMode({ id: 'idle' });
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[processorsDispatch, setEditorMode]
|
|
||||||
);
|
|
||||||
|
|
||||||
const movingProcessor = editorMode.id === 'movingProcessor' ? editorMode.arg : undefined;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="pipelineProcessorsEditor">
|
|
||||||
<EuiFlexGroup gutterSize="m" responsive={false} direction="column">
|
|
||||||
<EuiFlexItem grow={false}>
|
|
||||||
<ProcessorsTitleAndTestButton
|
|
||||||
onTestPipelineClick={onTestPipelineClick}
|
|
||||||
isTestButtonDisabled={isTestButtonDisabled}
|
|
||||||
/>
|
|
||||||
</EuiFlexItem>
|
|
||||||
<EuiFlexItem grow={false}>
|
|
||||||
<ProcessorsTree
|
|
||||||
baseSelector={PROCESSORS_BASE_SELECTOR}
|
|
||||||
processors={processors}
|
|
||||||
onAction={onTreeAction}
|
|
||||||
movingProcessor={movingProcessor}
|
|
||||||
/>
|
|
||||||
</EuiFlexItem>
|
|
||||||
<EuiFlexItem>
|
|
||||||
<EuiSpacer size="s" />
|
|
||||||
</EuiFlexItem>
|
|
||||||
<EuiFlexItem grow={false}>
|
|
||||||
<OnFailureProcessorsTitle />
|
|
||||||
</EuiFlexItem>
|
|
||||||
<EuiFlexItem grow={false}>
|
|
||||||
<EuiSwitch
|
|
||||||
label={
|
|
||||||
<FormattedMessage
|
|
||||||
id="xpack.ingestPipelines.pipelineEditor.onFailureToggleDescription"
|
|
||||||
defaultMessage="Add failure processors"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
checked={showGlobalOnFailure}
|
|
||||||
onChange={(e) => setShowGlobalOnFailure(e.target.checked)}
|
|
||||||
data-test-subj="pipelineEditorOnFailureToggle"
|
|
||||||
/>
|
|
||||||
</EuiFlexItem>
|
|
||||||
{showGlobalOnFailure ? (
|
|
||||||
<EuiFlexItem grow={false}>
|
|
||||||
<ProcessorsTree
|
|
||||||
data-test-subj="pipelineEditorOnFailureTree"
|
|
||||||
baseSelector={ON_FAILURE_BASE_SELECTOR}
|
|
||||||
processors={onFailureProcessors}
|
|
||||||
onAction={onTreeAction}
|
|
||||||
movingProcessor={movingProcessor}
|
|
||||||
/>
|
|
||||||
</EuiFlexItem>
|
|
||||||
) : undefined}
|
|
||||||
</EuiFlexGroup>
|
|
||||||
{editorMode.id === 'editingProcessor' || editorMode.id === 'creatingProcessor' ? (
|
|
||||||
<ProcessorSettingsForm
|
|
||||||
isOnFailure={isOnFailureSelector(editorMode.arg.selector)}
|
|
||||||
processor={editorMode.id === 'editingProcessor' ? editorMode.arg.processor : undefined}
|
|
||||||
onOpen={onFlyoutOpen}
|
|
||||||
onFormUpdate={onFormUpdate}
|
|
||||||
onSubmit={onSubmit}
|
|
||||||
onClose={onCloseSettingsForm}
|
|
||||||
/>
|
|
||||||
) : undefined}
|
|
||||||
{editorMode.id === 'removingProcessor' && (
|
|
||||||
<ProcessorRemoveModal
|
|
||||||
selector={editorMode.arg.selector}
|
|
||||||
processor={getValue(editorMode.arg.selector, {
|
|
||||||
processors,
|
|
||||||
onFailure: onFailureProcessors,
|
|
||||||
})}
|
|
||||||
onResult={({ confirmed, selector }) => {
|
|
||||||
if (confirmed) {
|
|
||||||
processorsDispatch({
|
|
||||||
type: 'removeProcessor',
|
|
||||||
payload: { selector },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
setEditorMode({ id: 'idle' });
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
|
@ -12,6 +12,6 @@ export {
|
||||||
Action,
|
Action,
|
||||||
} from './processors_reducer';
|
} from './processors_reducer';
|
||||||
|
|
||||||
export { ON_FAILURE_STATE_SCOPE, PROCESSOR_STATE_SCOPE } from './constants';
|
export * from './constants';
|
||||||
|
|
||||||
export { isChildPath, isOnFailureSelector } from './utils';
|
export { isChildPath, isOnFailureSelector } from './utils';
|
||||||
|
|
|
@ -38,6 +38,12 @@ export type Action =
|
||||||
payload: {
|
payload: {
|
||||||
source: ProcessorSelector;
|
source: ProcessorSelector;
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'loadProcessors';
|
||||||
|
payload: {
|
||||||
|
newState: DeserializeResult;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ProcessorsDispatch = Dispatch<Action>;
|
export type ProcessorsDispatch = Dispatch<Action>;
|
||||||
|
@ -124,6 +130,14 @@ export const reducer: Reducer<State, Action> = (state, action) => {
|
||||||
return setValue(sourceProcessorsArraySelector, state, sourceProcessorsArray);
|
return setValue(sourceProcessorsArraySelector, state, sourceProcessorsArray);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (action.type === 'loadProcessors') {
|
||||||
|
return {
|
||||||
|
...action.payload.newState,
|
||||||
|
onFailure: action.payload.newState.onFailure ?? [],
|
||||||
|
isRoot: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return state;
|
return state;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,8 @@ export interface OnUpdateHandlerArg extends FormValidityState {
|
||||||
getData: () => SerializeResult;
|
getData: () => SerializeResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type OnUpdateHandler = (arg: OnUpdateHandlerArg) => void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The editor can be in different modes. This enables us to hold
|
* The editor can be in different modes. This enables us to hold
|
||||||
* a reference to data dispatch to the reducer (like the {@link ProcessorSelector}
|
* a reference to data dispatch to the reducer (like the {@link ProcessorSelector}
|
||||||
|
|
|
@ -22,6 +22,8 @@ export {
|
||||||
UseRequestConfig,
|
UseRequestConfig,
|
||||||
WithPrivileges,
|
WithPrivileges,
|
||||||
Monaco,
|
Monaco,
|
||||||
|
JsonEditor,
|
||||||
|
OnJsonEditorUpdateHandler,
|
||||||
} from '../../../../src/plugins/es_ui_shared/public/';
|
} from '../../../../src/plugins/es_ui_shared/public/';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
|
|
@ -8469,13 +8469,7 @@
|
||||||
"xpack.ingestPipelines.form.nameFieldLabel": "名前",
|
"xpack.ingestPipelines.form.nameFieldLabel": "名前",
|
||||||
"xpack.ingestPipelines.form.nameTitle": "名前",
|
"xpack.ingestPipelines.form.nameTitle": "名前",
|
||||||
"xpack.ingestPipelines.form.onFailureFieldHelpText": "JSONフォーマットを使用:{code}",
|
"xpack.ingestPipelines.form.onFailureFieldHelpText": "JSONフォーマットを使用:{code}",
|
||||||
"xpack.ingestPipelines.form.onFailureFieldLabel": "障害プロセッサー(任意)",
|
|
||||||
"xpack.ingestPipelines.form.onFailureProcessorsJsonError": "入力が無効です。",
|
|
||||||
"xpack.ingestPipelines.form.pipelineNameRequiredError": "名前が必要です。",
|
"xpack.ingestPipelines.form.pipelineNameRequiredError": "名前が必要です。",
|
||||||
"xpack.ingestPipelines.form.processorsFieldHelpText": "JSONフォーマットを使用:{code}",
|
|
||||||
"xpack.ingestPipelines.form.processorsFieldLabel": "プロセッサー",
|
|
||||||
"xpack.ingestPipelines.form.processorsJsonError": "入力が無効です。",
|
|
||||||
"xpack.ingestPipelines.form.processorsRequiredError": "プロセッサーが必要です。",
|
|
||||||
"xpack.ingestPipelines.form.saveButtonLabel": "パイプラインを保存",
|
"xpack.ingestPipelines.form.saveButtonLabel": "パイプラインを保存",
|
||||||
"xpack.ingestPipelines.form.savePipelineError": "パイプラインを作成できません",
|
"xpack.ingestPipelines.form.savePipelineError": "パイプラインを作成できません",
|
||||||
"xpack.ingestPipelines.form.savingButtonLabel": "保存中…",
|
"xpack.ingestPipelines.form.savingButtonLabel": "保存中…",
|
||||||
|
|
|
@ -8473,13 +8473,7 @@
|
||||||
"xpack.ingestPipelines.form.nameFieldLabel": "名称",
|
"xpack.ingestPipelines.form.nameFieldLabel": "名称",
|
||||||
"xpack.ingestPipelines.form.nameTitle": "名称",
|
"xpack.ingestPipelines.form.nameTitle": "名称",
|
||||||
"xpack.ingestPipelines.form.onFailureFieldHelpText": "使用 JSON 格式:{code}",
|
"xpack.ingestPipelines.form.onFailureFieldHelpText": "使用 JSON 格式:{code}",
|
||||||
"xpack.ingestPipelines.form.onFailureFieldLabel": "失败处理器(可选)",
|
|
||||||
"xpack.ingestPipelines.form.onFailureProcessorsJsonError": "输入无效。",
|
|
||||||
"xpack.ingestPipelines.form.pipelineNameRequiredError": "“名称”必填。",
|
"xpack.ingestPipelines.form.pipelineNameRequiredError": "“名称”必填。",
|
||||||
"xpack.ingestPipelines.form.processorsFieldHelpText": "使用 JSON 格式:{code}",
|
|
||||||
"xpack.ingestPipelines.form.processorsFieldLabel": "处理器",
|
|
||||||
"xpack.ingestPipelines.form.processorsJsonError": "输入无效。",
|
|
||||||
"xpack.ingestPipelines.form.processorsRequiredError": "需要指定处理器。",
|
|
||||||
"xpack.ingestPipelines.form.saveButtonLabel": "保存管道",
|
"xpack.ingestPipelines.form.saveButtonLabel": "保存管道",
|
||||||
"xpack.ingestPipelines.form.savePipelineError": "无法创建管道",
|
"xpack.ingestPipelines.form.savePipelineError": "无法创建管道",
|
||||||
"xpack.ingestPipelines.form.savingButtonLabel": "正在保存......",
|
"xpack.ingestPipelines.form.savingButtonLabel": "正在保存......",
|
||||||
|
|
Loading…
Reference in a new issue