[Ingest Node Pipelines] Refactor pipeline simulator code (#72328)
This commit is contained in:
parent
f9fc83fb1d
commit
bf22fe54e1
|
@ -4,4 +4,4 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export { PipelineFormProvider as PipelineForm } from './pipeline_form_provider';
|
||||
export { PipelineForm } from './pipeline_form';
|
||||
|
|
|
@ -16,7 +16,6 @@ import './pipeline_form.scss';
|
|||
import { OnUpdateHandlerArg, OnUpdateHandler } from '../pipeline_processors_editor';
|
||||
|
||||
import { PipelineRequestFlyout } from './pipeline_request_flyout';
|
||||
import { PipelineTestFlyout } from './pipeline_test_flyout';
|
||||
import { PipelineFormFields } from './pipeline_form_fields';
|
||||
import { PipelineFormError } from './pipeline_form_error';
|
||||
import { pipelineFormSchema } from './schema';
|
||||
|
@ -48,8 +47,6 @@ export const PipelineForm: React.FunctionComponent<PipelineFormProps> = ({
|
|||
}) => {
|
||||
const [isRequestVisible, setIsRequestVisible] = useState<boolean>(false);
|
||||
|
||||
const [isTestingPipeline, setIsTestingPipeline] = useState<boolean>(false);
|
||||
|
||||
const {
|
||||
processors: initialProcessors,
|
||||
on_failure: initialOnFailureProcessors,
|
||||
|
@ -79,10 +76,6 @@ export const PipelineForm: React.FunctionComponent<PipelineFormProps> = ({
|
|||
}
|
||||
};
|
||||
|
||||
const handleTestPipelineClick = () => {
|
||||
setIsTestingPipeline(true);
|
||||
};
|
||||
|
||||
const { form } = useForm<IPipelineForm>({
|
||||
schema: pipelineFormSchema,
|
||||
defaultValue: defaultFormValues,
|
||||
|
@ -90,7 +83,6 @@ export const PipelineForm: React.FunctionComponent<PipelineFormProps> = ({
|
|||
});
|
||||
|
||||
const onEditorFlyoutOpen = useCallback(() => {
|
||||
setIsTestingPipeline(false);
|
||||
setIsRequestVisible(false);
|
||||
}, [setIsRequestVisible]);
|
||||
|
||||
|
@ -137,8 +129,6 @@ export const PipelineForm: React.FunctionComponent<PipelineFormProps> = ({
|
|||
onFailure={processorsState.onFailure}
|
||||
onProcessorsUpdate={onProcessorsChangeHandler}
|
||||
hasVersion={Boolean(defaultValue.version)}
|
||||
isTestButtonDisabled={isTestingPipeline || form.isValid === false}
|
||||
onTestPipelineClick={handleTestPipelineClick}
|
||||
isEditing={isEditing}
|
||||
/>
|
||||
|
||||
|
@ -198,18 +188,6 @@ export const PipelineForm: React.FunctionComponent<PipelineFormProps> = ({
|
|||
closeFlyout={() => setIsRequestVisible((prevIsRequestVisible) => !prevIsRequestVisible)}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{/* Test pipeline flyout */}
|
||||
{isTestingPipeline ? (
|
||||
<PipelineTestFlyout
|
||||
readProcessors={() =>
|
||||
processorStateRef.current?.getData() || { processors: [], on_failure: [] }
|
||||
}
|
||||
closeFlyout={() => {
|
||||
setIsTestingPipeline((prevIsTestingPipeline) => !prevIsTestingPipeline);
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
</Form>
|
||||
|
||||
<EuiSpacer size="m" />
|
||||
|
|
|
@ -13,7 +13,7 @@ import { Processor } from '../../../../common/types';
|
|||
import { getUseField, getFormRow, Field, useKibana } from '../../../shared_imports';
|
||||
|
||||
import {
|
||||
PipelineProcessorsContextProvider,
|
||||
ProcessorsEditorContextProvider,
|
||||
GlobalOnFailureProcessorsEditor,
|
||||
ProcessorsEditor,
|
||||
OnUpdateHandler,
|
||||
|
@ -29,8 +29,6 @@ interface Props {
|
|||
onLoadJson: OnDoneLoadJsonHandler;
|
||||
onProcessorsUpdate: OnUpdateHandler;
|
||||
hasVersion: boolean;
|
||||
isTestButtonDisabled: boolean;
|
||||
onTestPipelineClick: () => void;
|
||||
onEditorFlyoutOpen: () => void;
|
||||
isEditing?: boolean;
|
||||
}
|
||||
|
@ -45,8 +43,6 @@ export const PipelineFormFields: React.FunctionComponent<Props> = ({
|
|||
onProcessorsUpdate,
|
||||
isEditing,
|
||||
hasVersion,
|
||||
isTestButtonDisabled,
|
||||
onTestPipelineClick,
|
||||
onEditorFlyoutOpen,
|
||||
}) => {
|
||||
const { services } = useKibana();
|
||||
|
@ -125,20 +121,18 @@ export const PipelineFormFields: React.FunctionComponent<Props> = ({
|
|||
|
||||
{/* Pipeline Processors Editor */}
|
||||
|
||||
<PipelineProcessorsContextProvider
|
||||
<ProcessorsEditorContextProvider
|
||||
onFlyoutOpen={onEditorFlyoutOpen}
|
||||
links={{ esDocsBasePath: services.documentation.getEsDocsBasePath() }}
|
||||
api={services.api}
|
||||
toasts={services.notifications.toasts}
|
||||
onUpdate={onProcessorsUpdate}
|
||||
value={{ processors, onFailure }}
|
||||
>
|
||||
<div className="pipelineProcessorsEditor">
|
||||
<EuiFlexGroup gutterSize="m" responsive={false} direction="column">
|
||||
<EuiFlexItem grow={false}>
|
||||
<ProcessorsHeader
|
||||
onLoadJson={onLoadJson}
|
||||
onTestPipelineClick={onTestPipelineClick}
|
||||
isTestButtonDisabled={isTestButtonDisabled}
|
||||
/>
|
||||
<ProcessorsHeader onLoadJson={onLoadJson} />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<ProcessorsEditor />
|
||||
|
@ -154,7 +148,7 @@ export const PipelineFormFields: React.FunctionComponent<Props> = ({
|
|||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</div>
|
||||
</PipelineProcessorsContextProvider>
|
||||
</ProcessorsEditorContextProvider>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,20 +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 { PipelineForm as PipelineFormUI, PipelineFormProps } from './pipeline_form';
|
||||
import { TestConfigContextProvider } from './test_config_context';
|
||||
|
||||
export const PipelineFormProvider: React.FunctionComponent<PipelineFormProps> = (
|
||||
passThroughProps
|
||||
) => {
|
||||
return (
|
||||
<TestConfigContextProvider>
|
||||
<PipelineFormUI {...passThroughProps} />
|
||||
</TestConfigContextProvider>
|
||||
);
|
||||
};
|
|
@ -1,203 +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, { useState, useEffect, useCallback } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import {
|
||||
EuiFlyout,
|
||||
EuiFlyoutBody,
|
||||
EuiFlyoutHeader,
|
||||
EuiSpacer,
|
||||
EuiTitle,
|
||||
EuiCallOut,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { useKibana } from '../../../../shared_imports';
|
||||
import { Pipeline } from '../../../../../common/types';
|
||||
import { Tabs, Tab, OutputTab, DocumentsTab } from './tabs';
|
||||
import { useTestConfigContext } from '../test_config_context';
|
||||
|
||||
export interface PipelineTestFlyoutProps {
|
||||
closeFlyout: () => void;
|
||||
pipeline: Pipeline;
|
||||
isPipelineValid: boolean;
|
||||
}
|
||||
|
||||
export const PipelineTestFlyout: React.FunctionComponent<PipelineTestFlyoutProps> = ({
|
||||
closeFlyout,
|
||||
pipeline,
|
||||
isPipelineValid,
|
||||
}) => {
|
||||
const { services } = useKibana();
|
||||
|
||||
const { testConfig } = useTestConfigContext();
|
||||
const { documents: cachedDocuments, verbose: cachedVerbose } = testConfig;
|
||||
|
||||
const initialSelectedTab = cachedDocuments ? 'output' : 'documents';
|
||||
const [selectedTab, setSelectedTab] = useState<Tab>(initialSelectedTab);
|
||||
|
||||
const [shouldExecuteImmediately, setShouldExecuteImmediately] = useState<boolean>(false);
|
||||
const [isExecuting, setIsExecuting] = useState<boolean>(false);
|
||||
const [executeError, setExecuteError] = useState<any>(null);
|
||||
const [executeOutput, setExecuteOutput] = useState<any>(undefined);
|
||||
|
||||
const handleExecute = useCallback(
|
||||
async (documents: object[], verbose?: boolean) => {
|
||||
const { name: pipelineName, ...pipelineDefinition } = pipeline;
|
||||
|
||||
setIsExecuting(true);
|
||||
setExecuteError(null);
|
||||
|
||||
const { error, data: output } = await services.api.simulatePipeline({
|
||||
documents,
|
||||
verbose,
|
||||
pipeline: pipelineDefinition,
|
||||
});
|
||||
|
||||
setIsExecuting(false);
|
||||
|
||||
if (error) {
|
||||
setExecuteError(error);
|
||||
return;
|
||||
}
|
||||
|
||||
setExecuteOutput(output);
|
||||
|
||||
services.notifications.toasts.addSuccess(
|
||||
i18n.translate('xpack.ingestPipelines.testPipelineFlyout.successNotificationText', {
|
||||
defaultMessage: 'Pipeline executed',
|
||||
}),
|
||||
{
|
||||
toastLifeTimeMs: 1000,
|
||||
}
|
||||
);
|
||||
|
||||
setSelectedTab('output');
|
||||
},
|
||||
[pipeline, services.api, services.notifications.toasts]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (cachedDocuments) {
|
||||
setShouldExecuteImmediately(true);
|
||||
}
|
||||
// We only want to know on initial mount if there are cached documents
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
// If the user has already tested the pipeline once,
|
||||
// use the cached test config and automatically execute the pipeline
|
||||
if (shouldExecuteImmediately && Object.entries(pipeline).length > 0) {
|
||||
setShouldExecuteImmediately(false);
|
||||
handleExecute(cachedDocuments!, cachedVerbose);
|
||||
}
|
||||
}, [
|
||||
pipeline,
|
||||
handleExecute,
|
||||
cachedDocuments,
|
||||
cachedVerbose,
|
||||
isExecuting,
|
||||
shouldExecuteImmediately,
|
||||
]);
|
||||
|
||||
let tabContent;
|
||||
|
||||
if (selectedTab === 'output') {
|
||||
tabContent = (
|
||||
<OutputTab
|
||||
executeOutput={executeOutput}
|
||||
handleExecute={handleExecute}
|
||||
isExecuting={isExecuting}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
// default to "documents" tab
|
||||
tabContent = (
|
||||
<DocumentsTab
|
||||
isExecuting={isExecuting}
|
||||
isPipelineValid={isPipelineValid}
|
||||
handleExecute={handleExecute}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiFlyout maxWidth={550} onClose={closeFlyout} data-test-subj="testPipelineFlyout">
|
||||
<EuiFlyoutHeader>
|
||||
<EuiTitle>
|
||||
<h2 data-test-subj="title">
|
||||
{pipeline.name ? (
|
||||
<FormattedMessage
|
||||
id="xpack.ingestPipelines.testPipelineFlyout.withPipelineNameTitle"
|
||||
defaultMessage="Test pipeline '{pipelineName}'"
|
||||
values={{
|
||||
pipelineName: pipeline.name,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="xpack.ingestPipelines.testPipelineFlyout.title"
|
||||
defaultMessage="Test pipeline"
|
||||
/>
|
||||
)}
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
</EuiFlyoutHeader>
|
||||
|
||||
<EuiFlyoutBody>
|
||||
<Tabs
|
||||
onTabChange={setSelectedTab}
|
||||
selectedTab={selectedTab}
|
||||
getIsDisabled={(tabId) => !executeOutput && tabId === 'output'}
|
||||
/>
|
||||
|
||||
<EuiSpacer />
|
||||
|
||||
{/* Execute error */}
|
||||
{executeError ? (
|
||||
<>
|
||||
<EuiCallOut
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.ingestPipelines.testPipelineFlyout.executePipelineError"
|
||||
defaultMessage="Unable to execute pipeline"
|
||||
/>
|
||||
}
|
||||
color="danger"
|
||||
iconType="alert"
|
||||
>
|
||||
<p>{executeError.message}</p>
|
||||
</EuiCallOut>
|
||||
<EuiSpacer size="m" />
|
||||
</>
|
||||
) : null}
|
||||
|
||||
{/* Invalid pipeline error */}
|
||||
{!isPipelineValid ? (
|
||||
<>
|
||||
<EuiCallOut
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.ingestPipelines.testPipelineFlyout.invalidPipelineErrorMessage"
|
||||
defaultMessage="The pipeline to execute is invalid."
|
||||
/>
|
||||
}
|
||||
color="danger"
|
||||
iconType="alert"
|
||||
/>
|
||||
<EuiSpacer size="m" />
|
||||
</>
|
||||
) : null}
|
||||
|
||||
{/* Documents or output tab content */}
|
||||
{tabContent}
|
||||
</EuiFlyoutBody>
|
||||
</EuiFlyout>
|
||||
);
|
||||
};
|
|
@ -1,47 +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, { useState, useEffect } from 'react';
|
||||
|
||||
import { Pipeline } from '../../../../../common/types';
|
||||
import { useFormContext } from '../../../../shared_imports';
|
||||
|
||||
import { ReadProcessorsFunction } from '../types';
|
||||
|
||||
import { PipelineTestFlyout, PipelineTestFlyoutProps } from './pipeline_test_flyout';
|
||||
|
||||
interface Props extends Omit<PipelineTestFlyoutProps, 'pipeline' | 'isPipelineValid'> {
|
||||
readProcessors: ReadProcessorsFunction;
|
||||
}
|
||||
|
||||
export const PipelineTestFlyoutProvider: React.FunctionComponent<Props> = ({
|
||||
closeFlyout,
|
||||
readProcessors,
|
||||
}) => {
|
||||
const form = useFormContext();
|
||||
const [formData, setFormData] = useState<Pipeline>({} as Pipeline);
|
||||
const [isFormDataValid, setIsFormDataValid] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
const subscription = form.subscribe(async ({ isValid, validate, data }) => {
|
||||
const isFormValid = isValid ?? (await validate());
|
||||
if (isFormValid) {
|
||||
setFormData(data.format() as Pipeline);
|
||||
}
|
||||
setIsFormDataValid(isFormValid);
|
||||
});
|
||||
|
||||
return subscription.unsubscribe;
|
||||
}, [form]);
|
||||
|
||||
return (
|
||||
<PipelineTestFlyout
|
||||
pipeline={{ ...formData, ...readProcessors() }}
|
||||
closeFlyout={closeFlyout}
|
||||
isPipelineValid={isFormDataValid}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -5,25 +5,23 @@
|
|||
*/
|
||||
|
||||
import React, { FunctionComponent } from 'react';
|
||||
import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiLink, EuiText, EuiTitle } from '@elastic/eui';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiLink, EuiText, EuiTitle } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
import { usePipelineProcessorsContext } from '../pipeline_processors_editor/context';
|
||||
|
||||
import { LoadFromJsonButton, OnDoneLoadJsonHandler } from '../pipeline_processors_editor';
|
||||
import {
|
||||
LoadFromJsonButton,
|
||||
OnDoneLoadJsonHandler,
|
||||
TestPipelineButton,
|
||||
} from '../pipeline_processors_editor';
|
||||
|
||||
export interface Props {
|
||||
onTestPipelineClick: () => void;
|
||||
isTestButtonDisabled: boolean;
|
||||
onLoadJson: OnDoneLoadJsonHandler;
|
||||
}
|
||||
|
||||
export const ProcessorsHeader: FunctionComponent<Props> = ({
|
||||
onTestPipelineClick,
|
||||
isTestButtonDisabled,
|
||||
onLoadJson,
|
||||
}) => {
|
||||
export const ProcessorsHeader: FunctionComponent<Props> = ({ onLoadJson }) => {
|
||||
const { links } = usePipelineProcessorsContext();
|
||||
return (
|
||||
<EuiFlexGroup
|
||||
|
@ -63,17 +61,7 @@ export const ProcessorsHeader: FunctionComponent<Props> = ({
|
|||
<LoadFromJsonButton onDone={onLoadJson} />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
data-test-subj="testPipelineButton"
|
||||
size="s"
|
||||
onClick={onTestPipelineClick}
|
||||
disabled={isTestButtonDisabled}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestPipelines.pipelineEditor.testPipelineButtonLabel"
|
||||
defaultMessage="Test pipeline"
|
||||
/>
|
||||
</EuiButton>
|
||||
<TestPipelineButton />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
|
|
|
@ -7,7 +7,7 @@ import { act } from 'react-dom/test-utils';
|
|||
import React from 'react';
|
||||
import { registerTestBed, TestBed } from '../../../../../../../test_utils';
|
||||
import {
|
||||
PipelineProcessorsContextProvider,
|
||||
ProcessorsEditorContextProvider,
|
||||
Props,
|
||||
ProcessorsEditor,
|
||||
GlobalOnFailureProcessorsEditor,
|
||||
|
@ -62,9 +62,9 @@ jest.mock('react-virtualized', () => {
|
|||
|
||||
const testBedSetup = registerTestBed<TestSubject>(
|
||||
(props: Props) => (
|
||||
<PipelineProcessorsContextProvider {...props}>
|
||||
<ProcessorsEditorContextProvider {...props}>
|
||||
<ProcessorsEditor /> <GlobalOnFailureProcessorsEditor />
|
||||
</PipelineProcessorsContextProvider>
|
||||
</ProcessorsEditorContextProvider>
|
||||
),
|
||||
{
|
||||
doMountAsync: false,
|
||||
|
|
|
@ -3,8 +3,11 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import { notificationServiceMock } from 'src/core/public/mocks';
|
||||
|
||||
import { setup, SetupResult } from './pipeline_processors_editor.helpers';
|
||||
import { Pipeline } from '../../../../../common/types';
|
||||
import { apiService } from '../../../services';
|
||||
|
||||
const testProcessors: Pick<Pipeline, 'processors'> = {
|
||||
processors: [
|
||||
|
@ -46,6 +49,8 @@ describe('Pipeline Editor', () => {
|
|||
links: {
|
||||
esDocsBasePath: 'test',
|
||||
},
|
||||
toasts: notificationServiceMock.createSetupContract().toasts,
|
||||
api: apiService,
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -20,4 +20,6 @@ export { ProcessorRemoveModal } from './processor_remove_modal';
|
|||
|
||||
export { OnDoneLoadJsonHandler, LoadFromJsonButton } from './load_from_json';
|
||||
|
||||
export { TestPipelineButton } from './test_pipeline';
|
||||
|
||||
export { PipelineProcessorsItemTooltip, Position } from './pipeline_processors_editor_item_tooltip';
|
||||
|
|
|
@ -21,6 +21,7 @@ export const PipelineProcessorsEditor: FunctionComponent<Props> = memo(
|
|||
state: { editor, processors },
|
||||
} = usePipelineProcessorsContext();
|
||||
const baseSelector = useMemo(() => [stateSlice], [stateSlice]);
|
||||
|
||||
return (
|
||||
<ProcessorsTree
|
||||
baseSelector={baseSelector}
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* 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 { FlyoutProvider } from './flyout_provider';
|
||||
|
||||
const i18nTexts = {
|
||||
buttonLabel: i18n.translate('xpack.ingestPipelines.pipelineEditor.testPipeline.buttonLabel', {
|
||||
defaultMessage: 'Test pipeline',
|
||||
}),
|
||||
};
|
||||
|
||||
export const TestPipelineButton: FunctionComponent = () => {
|
||||
return (
|
||||
<FlyoutProvider>
|
||||
{(openFlyout) => {
|
||||
return (
|
||||
<EuiButton size="s" onClick={openFlyout} data-test-subj="testPipelineButton">
|
||||
{i18nTexts.buttonLabel}
|
||||
</EuiButton>
|
||||
);
|
||||
}}
|
||||
</FlyoutProvider>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,171 @@
|
|||
/*
|
||||
* 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, { useState, useEffect, useCallback } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import {
|
||||
EuiFlyout,
|
||||
EuiFlyoutBody,
|
||||
EuiFlyoutHeader,
|
||||
EuiSpacer,
|
||||
EuiTitle,
|
||||
EuiCallOut,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { usePipelineProcessorsContext, useTestConfigContext } from '../../context';
|
||||
import { serialize } from '../../serialize';
|
||||
|
||||
import { Tabs, Tab, OutputTab, DocumentsTab } from './flyout_tabs';
|
||||
|
||||
export interface Props {
|
||||
children: (openFlyout: () => void) => React.ReactNode;
|
||||
}
|
||||
|
||||
export const FlyoutProvider: React.FunctionComponent<Props> = ({ children }) => {
|
||||
const {
|
||||
state: { processors },
|
||||
api,
|
||||
toasts,
|
||||
} = usePipelineProcessorsContext();
|
||||
|
||||
const serializedProcessors = serialize(processors.state);
|
||||
|
||||
const { testConfig } = useTestConfigContext();
|
||||
const { documents: cachedDocuments, verbose: cachedVerbose } = testConfig;
|
||||
|
||||
const [isFlyoutVisible, setIsFlyoutVisible] = useState(false);
|
||||
|
||||
const initialSelectedTab = cachedDocuments ? 'output' : 'documents';
|
||||
const [selectedTab, setSelectedTab] = useState<Tab>(initialSelectedTab);
|
||||
|
||||
const [shouldExecuteImmediately, setShouldExecuteImmediately] = useState<boolean>(false);
|
||||
const [isExecuting, setIsExecuting] = useState<boolean>(false);
|
||||
const [executeError, setExecuteError] = useState<any>(null);
|
||||
const [executeOutput, setExecuteOutput] = useState<any>(undefined);
|
||||
|
||||
const handleExecute = useCallback(
|
||||
async (documents: object[], verbose?: boolean) => {
|
||||
setIsExecuting(true);
|
||||
setExecuteError(null);
|
||||
|
||||
const { error, data: output } = await api.simulatePipeline({
|
||||
documents,
|
||||
verbose,
|
||||
pipeline: { ...serializedProcessors },
|
||||
});
|
||||
|
||||
setIsExecuting(false);
|
||||
|
||||
if (error) {
|
||||
setExecuteError(error);
|
||||
return;
|
||||
}
|
||||
|
||||
setExecuteOutput(output);
|
||||
|
||||
toasts.addSuccess(
|
||||
i18n.translate('xpack.ingestPipelines.testPipelineFlyout.successNotificationText', {
|
||||
defaultMessage: 'Pipeline executed',
|
||||
}),
|
||||
{
|
||||
toastLifeTimeMs: 1000,
|
||||
}
|
||||
);
|
||||
|
||||
setSelectedTab('output');
|
||||
},
|
||||
[serializedProcessors, api, toasts]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (isFlyoutVisible === false && cachedDocuments) {
|
||||
setShouldExecuteImmediately(true);
|
||||
}
|
||||
}, [isFlyoutVisible, cachedDocuments]);
|
||||
|
||||
useEffect(() => {
|
||||
// If the user has already tested the pipeline once,
|
||||
// use the cached test config and automatically execute the pipeline
|
||||
if (isFlyoutVisible && shouldExecuteImmediately && cachedDocuments) {
|
||||
setShouldExecuteImmediately(false);
|
||||
handleExecute(cachedDocuments!, cachedVerbose);
|
||||
}
|
||||
}, [handleExecute, cachedDocuments, cachedVerbose, isFlyoutVisible, shouldExecuteImmediately]);
|
||||
|
||||
let tabContent;
|
||||
|
||||
if (selectedTab === 'output') {
|
||||
tabContent = (
|
||||
<OutputTab
|
||||
executeOutput={executeOutput}
|
||||
handleExecute={handleExecute}
|
||||
isExecuting={isExecuting}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
// default to "Documents" tab
|
||||
tabContent = <DocumentsTab isExecuting={isExecuting} handleExecute={handleExecute} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{children(() => setIsFlyoutVisible(true))}
|
||||
|
||||
{isFlyoutVisible && (
|
||||
<EuiFlyout
|
||||
maxWidth={550}
|
||||
onClose={() => setIsFlyoutVisible(false)}
|
||||
data-test-subj="testPipelineFlyout"
|
||||
>
|
||||
<EuiFlyoutHeader>
|
||||
<EuiTitle>
|
||||
<h2 data-test-subj="title">
|
||||
<FormattedMessage
|
||||
id="xpack.ingestPipelines.testPipelineFlyout.title"
|
||||
defaultMessage="Test pipeline"
|
||||
/>
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
</EuiFlyoutHeader>
|
||||
|
||||
<EuiFlyoutBody>
|
||||
<Tabs
|
||||
onTabChange={setSelectedTab}
|
||||
selectedTab={selectedTab}
|
||||
getIsDisabled={(tabId) => !executeOutput && tabId === 'output'}
|
||||
/>
|
||||
|
||||
<EuiSpacer />
|
||||
|
||||
{/* Execute error */}
|
||||
{executeError ? (
|
||||
<>
|
||||
<EuiCallOut
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.ingestPipelines.testPipelineFlyout.executePipelineError"
|
||||
defaultMessage="Unable to execute pipeline"
|
||||
/>
|
||||
}
|
||||
color="danger"
|
||||
iconType="alert"
|
||||
>
|
||||
<p>{executeError.message}</p>
|
||||
</EuiCallOut>
|
||||
<EuiSpacer size="m" />
|
||||
</>
|
||||
) : null}
|
||||
|
||||
{/* Documents or output tab content */}
|
||||
{tabContent}
|
||||
</EuiFlyoutBody>
|
||||
</EuiFlyout>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -9,8 +9,8 @@ import { FormattedMessage } from '@kbn/i18n/react';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiCode } from '@elastic/eui';
|
||||
|
||||
import { FormSchema, fieldValidators, ValidationFuncArg } from '../../../../../shared_imports';
|
||||
import { parseJson, stringifyJson } from '../../../../lib';
|
||||
import { FormSchema, fieldValidators, ValidationFuncArg } from '../../../../../../shared_imports';
|
||||
import { parseJson, stringifyJson } from '../../../../../lib';
|
||||
|
||||
const { emptyField, isJsonField } = fieldValidators;
|
||||
|
|
@ -17,32 +17,27 @@ import {
|
|||
Form,
|
||||
useForm,
|
||||
FormConfig,
|
||||
useKibana,
|
||||
} from '../../../../../shared_imports';
|
||||
} from '../../../../../../shared_imports';
|
||||
|
||||
import { usePipelineProcessorsContext, useTestConfigContext, TestConfig } from '../../../context';
|
||||
|
||||
import { documentsSchema } from './schema';
|
||||
import { useTestConfigContext, TestConfig } from '../../test_config_context';
|
||||
|
||||
const UseField = getUseField({ component: Field });
|
||||
|
||||
interface Props {
|
||||
handleExecute: (documents: object[], verbose: boolean) => void;
|
||||
isPipelineValid: boolean;
|
||||
isExecuting: boolean;
|
||||
}
|
||||
|
||||
export const DocumentsTab: React.FunctionComponent<Props> = ({
|
||||
isPipelineValid,
|
||||
handleExecute,
|
||||
isExecuting,
|
||||
}) => {
|
||||
const { services } = useKibana();
|
||||
export const DocumentsTab: React.FunctionComponent<Props> = ({ handleExecute, isExecuting }) => {
|
||||
const { links } = usePipelineProcessorsContext();
|
||||
|
||||
const { setCurrentTestConfig, testConfig } = useTestConfigContext();
|
||||
const { verbose: cachedVerbose, documents: cachedDocuments } = testConfig;
|
||||
|
||||
const executePipeline: FormConfig['onSubmit'] = async (formData, isValid) => {
|
||||
if (!isValid || !isPipelineValid) {
|
||||
if (!isValid) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -76,7 +71,7 @@ export const DocumentsTab: React.FunctionComponent<Props> = ({
|
|||
values={{
|
||||
learnMoreLink: (
|
||||
<EuiLink
|
||||
href={services.documentation.getSimulatePipelineApiUrl()}
|
||||
href={`${links.esDocsBasePath}/simulate-pipeline-api.html`}
|
||||
target="_blank"
|
||||
external
|
||||
>
|
||||
|
@ -98,7 +93,7 @@ export const DocumentsTab: React.FunctionComponent<Props> = ({
|
|||
<Form
|
||||
form={form}
|
||||
data-test-subj="testPipelineForm"
|
||||
isInvalid={form.isSubmitted && !form.isValid && !isPipelineValid}
|
||||
isInvalid={form.isSubmitted && !form.isValid}
|
||||
error={form.getErrors()}
|
||||
>
|
||||
{/* Documents editor */}
|
||||
|
@ -125,7 +120,7 @@ export const DocumentsTab: React.FunctionComponent<Props> = ({
|
|||
onClick={form.submit}
|
||||
size="s"
|
||||
isLoading={isExecuting}
|
||||
disabled={(form.isSubmitted && !form.isValid) || !isPipelineValid}
|
||||
disabled={form.isSubmitted && !form.isValid}
|
||||
>
|
||||
{isExecuting ? (
|
||||
<FormattedMessage
|
|
@ -16,7 +16,8 @@ import {
|
|||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
} from '@elastic/eui';
|
||||
import { useTestConfigContext } from '../../test_config_context';
|
||||
|
||||
import { useTestConfigContext } from '../../../context';
|
||||
|
||||
interface Props {
|
||||
executeOutput?: { docs: object[] };
|
|
@ -4,4 +4,4 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export { PipelineTestFlyoutProvider as PipelineTestFlyout } from './pipeline_test_flyout_provider';
|
||||
export { TestPipelineButton } from './button';
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* 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 {
|
||||
PipelineProcessorsContextProvider,
|
||||
Props as ProcessorsContextProps,
|
||||
} from './processors_context';
|
||||
import { TestConfigContextProvider } from './test_config_context';
|
||||
|
||||
interface Props extends ProcessorsContextProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const ProcessorsEditorContextProvider: FunctionComponent<Props> = ({
|
||||
children,
|
||||
links,
|
||||
api,
|
||||
toasts,
|
||||
onUpdate,
|
||||
value,
|
||||
onFlyoutOpen,
|
||||
}: Props) => {
|
||||
return (
|
||||
<TestConfigContextProvider>
|
||||
<PipelineProcessorsContextProvider
|
||||
onFlyoutOpen={onFlyoutOpen}
|
||||
links={links}
|
||||
api={api}
|
||||
toasts={toasts}
|
||||
onUpdate={onUpdate}
|
||||
value={value}
|
||||
>
|
||||
{children}
|
||||
</PipelineProcessorsContextProvider>
|
||||
</TestConfigContextProvider>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* 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 { ProcessorsEditorContextProvider } from './context';
|
||||
|
||||
export { TestConfigContextProvider, useTestConfigContext, TestConfig } from './test_config_context';
|
||||
|
||||
export {
|
||||
PipelineProcessorsContextProvider,
|
||||
usePipelineProcessorsContext,
|
||||
Props,
|
||||
} from './processors_context';
|
|
@ -15,7 +15,10 @@ import React, {
|
|||
useRef,
|
||||
} from 'react';
|
||||
|
||||
import { Processor } from '../../../../common/types';
|
||||
import { NotificationsSetup } from 'src/core/public';
|
||||
|
||||
import { Processor } from '../../../../../common/types';
|
||||
import { ApiService } from '../../../services';
|
||||
|
||||
import {
|
||||
EditorMode,
|
||||
|
@ -26,29 +29,31 @@ import {
|
|||
ContextValueState,
|
||||
Links,
|
||||
ProcessorInternal,
|
||||
} from './types';
|
||||
} from '../types';
|
||||
|
||||
import { useProcessorsState, isOnFailureSelector } from './processors_reducer';
|
||||
import { useProcessorsState, isOnFailureSelector } from '../processors_reducer';
|
||||
|
||||
import { deserialize } from './deserialize';
|
||||
import { deserialize } from '../deserialize';
|
||||
|
||||
import { serialize } from './serialize';
|
||||
import { serialize } from '../serialize';
|
||||
|
||||
import { OnActionHandler } from './components/processors_tree';
|
||||
import { OnActionHandler } from '../components/processors_tree';
|
||||
|
||||
import {
|
||||
ProcessorRemoveModal,
|
||||
PipelineProcessorsItemTooltip,
|
||||
ProcessorSettingsForm,
|
||||
OnSubmitHandler,
|
||||
} from './components';
|
||||
} from '../components';
|
||||
|
||||
import { getValue } from './utils';
|
||||
import { getValue } from '../utils';
|
||||
|
||||
const PipelineProcessorsContext = createContext<ContextValue>({} as any);
|
||||
|
||||
export interface Props {
|
||||
links: Links;
|
||||
api: ApiService;
|
||||
toasts: NotificationsSetup['toasts'];
|
||||
value: {
|
||||
processors: Processor[];
|
||||
onFailure?: Processor[];
|
||||
|
@ -62,6 +67,8 @@ export interface Props {
|
|||
|
||||
export const PipelineProcessorsContextProvider: FunctionComponent<Props> = ({
|
||||
links,
|
||||
api,
|
||||
toasts,
|
||||
value: { processors: originalProcessors, onFailure: originalOnFailureProcessors },
|
||||
onUpdate,
|
||||
onFlyoutOpen,
|
||||
|
@ -205,6 +212,8 @@ export const PipelineProcessorsContextProvider: FunctionComponent<Props> = ({
|
|||
<PipelineProcessorsContext.Provider
|
||||
value={{
|
||||
links,
|
||||
api,
|
||||
toasts,
|
||||
onTreeAction,
|
||||
state,
|
||||
}}
|
|
@ -6,10 +6,12 @@
|
|||
|
||||
export { PipelineProcessorsContextProvider, Props } from './context';
|
||||
|
||||
export { ProcessorsEditorContextProvider } from './context';
|
||||
|
||||
export { ProcessorsEditor, GlobalOnFailureProcessorsEditor } from './editors';
|
||||
|
||||
export { OnUpdateHandlerArg, OnUpdateHandler } from './types';
|
||||
|
||||
export { SerializeResult } from './serialize';
|
||||
|
||||
export { LoadFromJsonButton, OnDoneLoadJsonHandler } from './components';
|
||||
export { LoadFromJsonButton, OnDoneLoadJsonHandler, TestPipelineButton } from './components';
|
||||
|
|
|
@ -5,7 +5,9 @@
|
|||
*/
|
||||
|
||||
import { Dispatch } from 'react';
|
||||
import { NotificationsSetup } from 'src/core/public';
|
||||
import { OnFormUpdateArg } from '../../../shared_imports';
|
||||
import { ApiService } from '../../services';
|
||||
import { SerializeResult } from './serialize';
|
||||
import { OnActionHandler, ProcessorInfo } from './components';
|
||||
import { ProcessorsDispatch, State as ProcessorsReducerState } from './processors_reducer';
|
||||
|
@ -75,6 +77,8 @@ export interface ContextValueState {
|
|||
|
||||
export interface ContextValue {
|
||||
links: Links;
|
||||
toasts: NotificationsSetup['toasts'];
|
||||
api: ApiService;
|
||||
onTreeAction: OnActionHandler;
|
||||
state: ContextValueState;
|
||||
}
|
||||
|
|
|
@ -108,7 +108,7 @@ export class ApiService {
|
|||
public async simulatePipeline(testConfig: {
|
||||
documents: object[];
|
||||
verbose?: boolean;
|
||||
pipeline: Omit<Pipeline, 'name'>;
|
||||
pipeline: Pick<Pipeline, 'processors' | 'on_failure'>;
|
||||
}) {
|
||||
const result = await this.sendRequest({
|
||||
path: `${API_BASE_PATH}/simulate`,
|
||||
|
|
|
@ -34,10 +34,6 @@ export class DocumentationService {
|
|||
public getPutPipelineApiUrl() {
|
||||
return `${this.esDocBasePath}/put-pipeline-api.html`;
|
||||
}
|
||||
|
||||
public getSimulatePipelineApiUrl() {
|
||||
return `${this.esDocBasePath}/simulate-pipeline-api.html`;
|
||||
}
|
||||
}
|
||||
|
||||
export const documentationService = new DocumentationService();
|
||||
|
|
|
@ -9799,7 +9799,6 @@
|
|||
"xpack.ingestPipelines.pipelineEditor.setForm.valueFieldLabel": "値",
|
||||
"xpack.ingestPipelines.pipelineEditor.setForm.valueRequiredError": "設定する値が必要です。",
|
||||
"xpack.ingestPipelines.pipelineEditor.settingsForm.learnMoreLabelLink.processor": "{processorLabel}ドキュメント",
|
||||
"xpack.ingestPipelines.pipelineEditor.testPipelineButtonLabel": "パイプラインをテスト",
|
||||
"xpack.ingestPipelines.pipelineEditor.typeField.fieldRequiredError": "タイプが必要です。",
|
||||
"xpack.ingestPipelines.pipelineEditor.typeField.typeFieldLabel": "プロセッサー",
|
||||
"xpack.ingestPipelines.processors.label.append": "末尾に追加",
|
||||
|
@ -9857,13 +9856,11 @@
|
|||
"xpack.ingestPipelines.testPipelineFlyout.documentsTab.simulateDocumentionLink": "詳細",
|
||||
"xpack.ingestPipelines.testPipelineFlyout.documentsTab.tabDescriptionText": "投入するパイプラインのドキュメントの配列を指定します。{learnMoreLink}",
|
||||
"xpack.ingestPipelines.testPipelineFlyout.executePipelineError": "パイプラインを実行できません",
|
||||
"xpack.ingestPipelines.testPipelineFlyout.invalidPipelineErrorMessage": "実行するパイプラインが無効です。",
|
||||
"xpack.ingestPipelines.testPipelineFlyout.outputTab.descriptionLinkLabel": "出力を更新",
|
||||
"xpack.ingestPipelines.testPipelineFlyout.outputTab.descriptionText": "出力データを表示するか、パイプライン経由で渡されるときに各プロセッサーがドキュメントにどのように影響するのかを確認します。",
|
||||
"xpack.ingestPipelines.testPipelineFlyout.outputTab.verboseSwitchLabel": "冗長出力を表示",
|
||||
"xpack.ingestPipelines.testPipelineFlyout.successNotificationText": "パイプラインが実行されました",
|
||||
"xpack.ingestPipelines.testPipelineFlyout.title": "パイプラインをテスト",
|
||||
"xpack.ingestPipelines.testPipelineFlyout.withPipelineNameTitle": "パイプライン'{pipelineName}'をテスト",
|
||||
"xpack.lens.app.docLoadingError": "保存されたドキュメントの保存中にエラーが発生",
|
||||
"xpack.lens.app.docSavingError": "ドキュメントの保存中にエラーが発生",
|
||||
"xpack.lens.app.indexPatternLoadingError": "インデックスパターンの読み込み中にエラーが発生",
|
||||
|
|
|
@ -9801,7 +9801,6 @@
|
|||
"xpack.ingestPipelines.pipelineEditor.setForm.valueFieldLabel": "值",
|
||||
"xpack.ingestPipelines.pipelineEditor.setForm.valueRequiredError": "需要设置值。",
|
||||
"xpack.ingestPipelines.pipelineEditor.settingsForm.learnMoreLabelLink.processor": "{processorLabel}文档",
|
||||
"xpack.ingestPipelines.pipelineEditor.testPipelineButtonLabel": "测试管道",
|
||||
"xpack.ingestPipelines.pipelineEditor.typeField.fieldRequiredError": "类型必填。",
|
||||
"xpack.ingestPipelines.pipelineEditor.typeField.typeFieldLabel": "处理器",
|
||||
"xpack.ingestPipelines.processors.label.append": "追加",
|
||||
|
@ -9859,13 +9858,11 @@
|
|||
"xpack.ingestPipelines.testPipelineFlyout.documentsTab.simulateDocumentionLink": "了解详情",
|
||||
"xpack.ingestPipelines.testPipelineFlyout.documentsTab.tabDescriptionText": "为管道提供要采集的一系列文档。{learnMoreLink}",
|
||||
"xpack.ingestPipelines.testPipelineFlyout.executePipelineError": "无法执行管道",
|
||||
"xpack.ingestPipelines.testPipelineFlyout.invalidPipelineErrorMessage": "要执行的管道无效。",
|
||||
"xpack.ingestPipelines.testPipelineFlyout.outputTab.descriptionLinkLabel": "刷新输出",
|
||||
"xpack.ingestPipelines.testPipelineFlyout.outputTab.descriptionText": "查看输出数据或了解文档通过管道时每个处理器对文档的影响。",
|
||||
"xpack.ingestPipelines.testPipelineFlyout.outputTab.verboseSwitchLabel": "查看详细输出",
|
||||
"xpack.ingestPipelines.testPipelineFlyout.successNotificationText": "管道已执行",
|
||||
"xpack.ingestPipelines.testPipelineFlyout.title": "测试管道",
|
||||
"xpack.ingestPipelines.testPipelineFlyout.withPipelineNameTitle": "测试管道“{pipelineName}”",
|
||||
"xpack.lens.app.docLoadingError": "加载已保存文档时出错",
|
||||
"xpack.lens.app.docSavingError": "保存文档时出错",
|
||||
"xpack.lens.app.indexPatternLoadingError": "加载索引模式时出错",
|
||||
|
|
Loading…
Reference in a new issue