diff --git a/x-pack/plugins/ingest_pipelines/common/lib/pipeline_serialization.test.ts b/x-pack/plugins/ingest_pipelines/common/lib/pipeline_serialization.test.ts
index 2e9147065ea15..65d6b6e30497f 100644
--- a/x-pack/plugins/ingest_pipelines/common/lib/pipeline_serialization.test.ts
+++ b/x-pack/plugins/ingest_pipelines/common/lib/pipeline_serialization.test.ts
@@ -47,7 +47,7 @@ describe('pipeline_serialization', () => {
},
},
],
- onFailure: [
+ on_failure: [
{
set: {
field: 'error.message',
diff --git a/x-pack/plugins/ingest_pipelines/common/lib/pipeline_serialization.ts b/x-pack/plugins/ingest_pipelines/common/lib/pipeline_serialization.ts
index 9fd41c5695881..572f655076015 100644
--- a/x-pack/plugins/ingest_pipelines/common/lib/pipeline_serialization.ts
+++ b/x-pack/plugins/ingest_pipelines/common/lib/pipeline_serialization.ts
@@ -10,17 +10,10 @@ export function deserializePipelines(pipelinesByName: PipelinesByName): Pipeline
const pipelineNames: string[] = Object.keys(pipelinesByName);
const deserializedPipelines = pipelineNames.map((name: string) => {
- const { description, version, processors, on_failure } = pipelinesByName[name];
-
- const pipeline = {
+ return {
+ ...pipelinesByName[name],
name,
- description,
- version,
- processors,
- onFailure: on_failure,
};
-
- return pipeline;
});
return deserializedPipelines;
diff --git a/x-pack/plugins/ingest_pipelines/common/types.ts b/x-pack/plugins/ingest_pipelines/common/types.ts
index 6e02922a71018..8d77359a7c3c5 100644
--- a/x-pack/plugins/ingest_pipelines/common/types.ts
+++ b/x-pack/plugins/ingest_pipelines/common/types.ts
@@ -15,7 +15,7 @@ export interface Pipeline {
description: string;
version?: number;
processors: Processor[];
- onFailure?: Processor[];
+ on_failure?: Processor[];
}
export interface PipelinesByName {
diff --git a/x-pack/plugins/ingest_pipelines/public/application/app.tsx b/x-pack/plugins/ingest_pipelines/public/application/app.tsx
index f3c6ccd161f66..87fe55eae91ec 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/app.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/app.tsx
@@ -7,7 +7,7 @@
import React from 'react';
import { HashRouter, Switch, Route } from 'react-router-dom';
import { BASE_PATH } from '../../common/constants';
-import { PipelinesList, PipelinesCreate } from './sections';
+import { PipelinesList, PipelinesCreate, PipelinesEdit } from './sections';
export const App = () => {
return (
@@ -21,5 +21,6 @@ export const AppWithoutRouter = () => (
+
);
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/index.ts b/x-pack/plugins/ingest_pipelines/public/application/components/index.ts
index 705dbe54618d6..39a9dc8d89e99 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/components/index.ts
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/index.ts
@@ -7,3 +7,5 @@
export { PipelineForm } from './pipeline_form';
export { SectionError } from './section_error';
+
+export { PipelineRequestFlyoutProvider as PipelineRequestFlyout } from './pipeline_request_flyout_provider';
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form.tsx
index 59f1f659dadea..10b7c3d4f0931 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form.tsx
@@ -6,7 +6,15 @@
import React, { useState } from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
-import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiSwitch, EuiLink } from '@elastic/eui';
+import {
+ EuiButton,
+ EuiButtonEmpty,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiSpacer,
+ EuiSwitch,
+ EuiLink,
+} from '@elastic/eui';
import {
useForm,
@@ -20,14 +28,16 @@ import {
} from '../../../shared_imports';
import { Pipeline } from '../../../../common/types';
-import { SectionError } from '../section_error';
+import { SectionError, PipelineRequestFlyout } from '../';
import { pipelineFormSchema } from './schema';
interface Props {
onSave: (pipeline: Pipeline) => void;
+ onCancel: () => void;
isSaving: boolean;
saveError: any;
defaultValue?: Pipeline;
+ isEditing?: boolean;
}
const UseField = getUseField({ component: Field });
@@ -38,17 +48,22 @@ export const PipelineForm: React.FunctionComponent = ({
name: '',
description: '',
processors: '',
- onFailure: '',
+ on_failure: '',
version: '',
},
onSave,
isSaving,
saveError,
+ isEditing,
+ onCancel,
}) => {
const { services } = useKibana();
- const [isVersionVisible, setIsVersionVisible] = useState(false);
- const [isOnFailureEditorVisible, setIsOnFailureEditorVisible] = useState(false);
+ const [isVersionVisible, setIsVersionVisible] = useState(Boolean(defaultValue.version));
+ const [isOnFailureEditorVisible, setIsOnFailureEditorVisible] = useState(
+ Boolean(defaultValue.on_failure)
+ );
+ const [isRequestVisible, setIsRequestVisible] = useState(false);
const handleSave: FormConfig['onSubmit'] = (formData, isValid) => {
if (isValid) {
@@ -62,24 +77,25 @@ export const PipelineForm: React.FunctionComponent = ({
onSubmit: handleSave,
});
+ const saveButtonLabel = isSaving ? (
+
+ ) : isEditing ? (
+
+ ) : (
+
+ );
+
return (
<>
- {saveError ? (
- <>
-
- }
- error={saveError}
- data-test-subj="savePipelineError"
- />
-
- >
- ) : null}
-
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/schema.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/schema.tsx
index 4bc3e6a543206..55ee62132cf52 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/schema.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/schema.tsx
@@ -93,7 +93,7 @@ export const pipelineFormSchema: FormSchema = {
},
],
},
- onFailure: {
+ on_failure: {
label: i18n.translate('xpack.ingestPipelines.form.onFailureFieldLabel', {
defaultMessage: 'On-failure processors (optional)',
}),
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_request_flyout.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_request_flyout.tsx
new file mode 100644
index 0000000000000..a5184a20630d5
--- /dev/null
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_request_flyout.tsx
@@ -0,0 +1,89 @@
+/*
+ * 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, { useRef } from 'react';
+import { FormattedMessage } from '@kbn/i18n/react';
+
+import {
+ EuiButtonEmpty,
+ EuiCodeBlock,
+ EuiFlyout,
+ EuiFlyoutBody,
+ EuiFlyoutFooter,
+ EuiFlyoutHeader,
+ EuiSpacer,
+ EuiText,
+ EuiTitle,
+} from '@elastic/eui';
+
+import { Pipeline } from '../../../common/types';
+
+interface Props {
+ pipeline: Pipeline;
+ closeFlyout: () => void;
+}
+
+export const PipelineRequestFlyout: React.FunctionComponent = ({
+ closeFlyout,
+ pipeline,
+}) => {
+ const { name, ...pipelineBody } = pipeline;
+ const endpoint = `PUT _ingest/pipeline/${name || ''}`;
+ const payload = JSON.stringify(pipelineBody, null, 2);
+ const request = `${endpoint}\n${payload}`;
+ // Hack so that copied-to-clipboard value updates as content changes
+ // Related issue: https://github.com/elastic/eui/issues/3321
+ const uuid = useRef(0);
+ uuid.current++;
+
+ return (
+
+
+
+
+ {name ? (
+
+ ) : (
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {request}
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_request_flyout_provider.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_request_flyout_provider.tsx
new file mode 100644
index 0000000000000..8f8d89772c964
--- /dev/null
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_request_flyout_provider.tsx
@@ -0,0 +1,29 @@
+/*
+ * 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 { PipelineRequestFlyout } from './pipeline_request_flyout';
+
+export const PipelineRequestFlyoutProvider = ({ closeFlyout }: { closeFlyout: () => void }) => {
+ const form = useFormContext();
+ const [formData, setFormData] = useState({} as Pipeline);
+
+ useEffect(() => {
+ const subscription = form.subscribe(async ({ isValid, validate, data }) => {
+ const isFormValid = isValid ?? (await validate());
+ if (isFormValid) {
+ setFormData(data.format() as Pipeline);
+ }
+ });
+
+ return subscription.unsubscribe;
+ }, [form]);
+
+ return ;
+};
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/section_error.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/section_error.tsx
index 317da95e24687..f9ae3d588331d 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/components/section_error.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/section_error.tsx
@@ -7,12 +7,6 @@
import { EuiCallOut } from '@elastic/eui';
import React from 'react';
-export interface Error {
- error: string;
- message: string;
- statusCode: number;
-}
-
interface Props {
title: React.ReactNode;
error: Error;
diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/index.ts b/x-pack/plugins/ingest_pipelines/public/application/sections/index.ts
index 30935bdd9c9c4..fde6106b508db 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/sections/index.ts
+++ b/x-pack/plugins/ingest_pipelines/public/application/sections/index.ts
@@ -7,3 +7,5 @@
export { PipelinesList } from './pipelines_list';
export { PipelinesCreate } from './pipelines_create';
+
+export { PipelinesEdit } from './pipelines_edit';
diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_create/pipelines_create.tsx b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_create/pipelines_create.tsx
index 6589d57994dbe..452b0fccde539 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_create/pipelines_create.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_create/pipelines_create.tsx
@@ -6,7 +6,15 @@
import React, { useState, useEffect } from 'react';
import { RouteComponentProps } from 'react-router-dom';
import { FormattedMessage } from '@kbn/i18n/react';
-import { EuiPageBody, EuiPageContent, EuiSpacer, EuiTitle } from '@elastic/eui';
+import {
+ EuiPageBody,
+ EuiPageContent,
+ EuiSpacer,
+ EuiTitle,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiButtonEmpty,
+} from '@elastic/eui';
import { BASE_PATH } from '../../../../common/constants';
import { Pipeline } from '../../../../common/types';
@@ -35,6 +43,10 @@ export const PipelinesCreate: React.FunctionComponent = ({
history.push(BASE_PATH);
};
+ const onCancel = () => {
+ history.push(BASE_PATH);
+ };
+
useEffect(() => {
services.breadcrumbs.setBreadcrumbs('create');
}, [services]);
@@ -43,17 +55,43 @@ export const PipelinesCreate: React.FunctionComponent = ({
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
);
diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_edit/index.ts b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_edit/index.ts
new file mode 100644
index 0000000000000..26458d23fd6d8
--- /dev/null
+++ b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_edit/index.ts
@@ -0,0 +1,7 @@
+/*
+ * 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 { PipelinesEdit } from './pipelines_edit';
diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_edit/pipelines_edit.tsx b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_edit/pipelines_edit.tsx
new file mode 100644
index 0000000000000..02eba9c4f620f
--- /dev/null
+++ b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_edit/pipelines_edit.tsx
@@ -0,0 +1,144 @@
+/*
+ * 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 { RouteComponentProps } from 'react-router-dom';
+import { FormattedMessage } from '@kbn/i18n/react';
+import {
+ EuiPageBody,
+ EuiPageContent,
+ EuiSpacer,
+ EuiTitle,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiButtonEmpty,
+} from '@elastic/eui';
+
+import { BASE_PATH } from '../../../../common/constants';
+import { Pipeline } from '../../../../common/types';
+import { useKibana, SectionLoading } from '../../../shared_imports';
+import { PipelineForm, SectionError } from '../../components';
+
+interface MatchParams {
+ name: string;
+}
+
+export const PipelinesEdit: React.FunctionComponent> = ({
+ match: {
+ params: { name },
+ },
+ history,
+}) => {
+ const { services } = useKibana();
+
+ const [isSaving, setIsSaving] = useState(false);
+ const [saveError, setSaveError] = useState(null);
+
+ const decodedPipelineName = decodeURI(decodeURIComponent(name));
+
+ const { error, data: pipeline, isLoading } = services.api.useLoadPipeline(decodedPipelineName);
+
+ const onSave = async (updatedPipeline: Pipeline) => {
+ setIsSaving(true);
+ setSaveError(null);
+
+ const { error: savePipelineError } = await services.api.updatePipeline(updatedPipeline);
+
+ setIsSaving(false);
+
+ if (savePipelineError) {
+ setSaveError(savePipelineError);
+ return;
+ }
+
+ history.push(BASE_PATH);
+ };
+
+ const onCancel = () => {
+ history.push(BASE_PATH);
+ };
+
+ useEffect(() => {
+ services.breadcrumbs.setBreadcrumbs('edit');
+ }, [services.breadcrumbs]);
+
+ let content;
+
+ if (isLoading) {
+ content = (
+
+
+
+ );
+ } else if (error) {
+ content = (
+
+ }
+ error={error}
+ data-test-subj="fetchPipelineError"
+ />
+ );
+ } else if (pipeline) {
+ content = (
+
+ );
+ }
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {content}
+
+
+ );
+};
diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/details.tsx b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/details.tsx
index 2fa13b5da43e2..798b9153a1644 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/details.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/details.tsx
@@ -24,7 +24,7 @@ import { PipelineDetailsJsonBlock } from './details_json_block';
export interface Props {
pipeline: Pipeline;
- onEditClick: () => void;
+ onEditClick: (pipelineName: string) => void;
onDeleteClick: () => void;
onClose: () => void;
}
@@ -80,7 +80,7 @@ export const PipelineDetails: FunctionComponent = ({
/>
{/* On Failure Processor JSON */}
- {pipeline.onFailure?.length && (
+ {pipeline.on_failure?.length && (
<>
= ({
defaultMessage: 'On failure processors JSON',
}
)}
- json={pipeline.onFailure}
+ json={pipeline.on_failure}
/>
>
)}
@@ -109,7 +109,7 @@ export const PipelineDetails: FunctionComponent = ({
-
+ onEditClick(pipeline.name)}>
{i18n.translate('xpack.ingestPipelines.list.pipelineDetails.editButtonLabel', {
defaultMessage: 'Edit',
})}
diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/empty_list.tsx b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/empty_list.tsx
index eacabb08eced3..45c09a944a74f 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/empty_list.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/empty_list.tsx
@@ -21,7 +21,7 @@ export const EmptyList: FunctionComponent = () => (
actions={
{i18n.translate('xpack.ingestPipelines.list.table.emptyPrompt.createButtonLabel', {
- defaultMessage: 'Create pipeline',
+ defaultMessage: 'Create a pipeline',
})}
}
diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/main.tsx b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/main.tsx
index 40972aace12e8..311c1c9d4c9e7 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/main.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/main.tsx
@@ -5,6 +5,7 @@
*/
import React, { useEffect, useState } from 'react';
+import { RouteComponentProps } from 'react-router-dom';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
@@ -21,6 +22,7 @@ import {
import { EuiSpacer, EuiText } from '@elastic/eui';
import { Pipeline } from '../../../../common/types';
+import { BASE_PATH } from '../../../../common/constants';
import { useKibana, SectionLoading } from '../../../shared_imports';
import { UIM_PIPELINES_LIST_LOAD } from '../../constants';
@@ -28,7 +30,7 @@ import { EmptyList } from './empty_list';
import { PipelineTable } from './table';
import { PipelineDetails } from './details';
-export const PipelinesList: React.FunctionComponent = () => {
+export const PipelinesList: React.FunctionComponent = ({ history }) => {
const { services } = useKibana();
const [selectedPipeline, setSelectedPipeline] = useState(undefined);
@@ -43,6 +45,10 @@ export const PipelinesList: React.FunctionComponent = () => {
let content: React.ReactNode;
+ const editPipeline = (name: string) => {
+ history.push(encodeURI(`${BASE_PATH}/edit/${encodeURIComponent(name)}`));
+ };
+
if (isLoading) {
content = (
@@ -55,10 +61,8 @@ export const PipelinesList: React.FunctionComponent = () => {
} else if (data?.length) {
content = (
{
- sendRequest();
- }}
- onEditPipelineClick={() => {}}
+ onReloadClick={sendRequest}
+ onEditPipelineClick={editPipeline}
onDeletePipelineClick={() => {}}
onViewPipelineClick={setSelectedPipeline}
pipelines={data}
@@ -106,7 +110,7 @@ export const PipelinesList: React.FunctionComponent = () => {
- {/* Error call out or pipeline table */}
+ {/* Error call out for pipeline table */}
{error ? (
{
pipeline={selectedPipeline}
onClose={() => setSelectedPipeline(undefined)}
onDeleteClick={() => {}}
- onEditClick={() => {}}
+ onEditClick={editPipeline}
/>
)}
>
diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/table.tsx b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/table.tsx
index 12693435f00be..45f539007cde3 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/table.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/table.tsx
@@ -13,7 +13,7 @@ import { Pipeline } from '../../../../common/types';
export interface Props {
pipelines: Pipeline[];
onReloadClick: () => void;
- onEditPipelineClick: (pipeline: Pipeline) => void;
+ onEditPipelineClick: (pipeineName: string) => void;
onDeletePipelineClick: (pipeline: Pipeline) => void;
onViewPipelineClick: (pipeline: Pipeline) => void;
}
@@ -85,7 +85,7 @@ export const PipelineTable: FunctionComponent = ({
),
type: 'icon',
icon: 'pencil',
- onClick: onEditPipelineClick,
+ onClick: ({ name }) => onEditPipelineClick(name),
},
{
name: i18n.translate('xpack.ingestPipelines.list.table.deleteActionLabel', {
diff --git a/x-pack/plugins/ingest_pipelines/public/application/services/api.ts b/x-pack/plugins/ingest_pipelines/public/application/services/api.ts
index 92673109b037e..48b925b02eeb4 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/services/api.ts
+++ b/x-pack/plugins/ingest_pipelines/public/application/services/api.ts
@@ -15,7 +15,7 @@ import {
useRequest as _useRequest,
} from '../../shared_imports';
import { UiMetricService } from './ui_metric';
-import { UIM_PIPELINE_CREATE } from '../constants';
+import { UIM_PIPELINE_CREATE, UIM_PIPELINE_UPDATE } from '../constants';
export class ApiService {
private client: HttpSetup | undefined;
@@ -28,11 +28,13 @@ export class ApiService {
return _useRequest(this.client, config);
}
- private sendRequest(config: SendRequestConfig): Promise {
+ private sendRequest(
+ config: SendRequestConfig
+ ): Promise> {
if (!this.client) {
throw new Error('Api service has not be initialized.');
}
- return _sendRequest(this.client, config);
+ return _sendRequest(this.client, config);
}
private trackUiMetric(eventName: string) {
@@ -54,6 +56,13 @@ export class ApiService {
});
}
+ public useLoadPipeline(name: string) {
+ return this.useRequest({
+ path: `${API_BASE_PATH}/${encodeURIComponent(name)}`,
+ method: 'get',
+ });
+ }
+
public async createPipeline(pipeline: Pipeline) {
const result = await this.sendRequest({
path: API_BASE_PATH,
@@ -65,6 +74,19 @@ export class ApiService {
return result;
}
+
+ public async updatePipeline(pipeline: Pipeline) {
+ const { name, ...body } = pipeline;
+ const result = await this.sendRequest({
+ path: `${API_BASE_PATH}/${encodeURIComponent(name)}`,
+ method: 'put',
+ body: JSON.stringify(body),
+ });
+
+ this.trackUiMetric(UIM_PIPELINE_UPDATE);
+
+ return result;
+ }
}
export const apiService = new ApiService();
diff --git a/x-pack/plugins/ingest_pipelines/public/application/services/breadcrumbs.ts b/x-pack/plugins/ingest_pipelines/public/application/services/breadcrumbs.ts
index 4d3d0d886e999..b6856355ddc27 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/services/breadcrumbs.ts
+++ b/x-pack/plugins/ingest_pipelines/public/application/services/breadcrumbs.ts
@@ -36,6 +36,17 @@ export class BreadcrumbService {
}),
},
],
+ edit: [
+ {
+ text: homeBreadcrumbText,
+ href: `#${BASE_PATH}`,
+ },
+ {
+ text: i18n.translate('xpack.ingestPipelines.breadcrumb.editPipelineLabel', {
+ defaultMessage: 'Edit pipeline',
+ }),
+ },
+ ],
};
private setBreadcrumbsHandler?: SetBreadcrumbs;
@@ -44,7 +55,7 @@ export class BreadcrumbService {
this.setBreadcrumbsHandler = setBreadcrumbsHandler;
}
- public setBreadcrumbs(type: 'create' | 'home'): void {
+ public setBreadcrumbs(type: 'create' | 'home' | 'edit'): void {
if (!this.setBreadcrumbsHandler) {
throw new Error('Breadcrumb service has not been initialized');
}
diff --git a/x-pack/plugins/ingest_pipelines/public/application/services/documentation.ts b/x-pack/plugins/ingest_pipelines/public/application/services/documentation.ts
index 78a9764be8e13..d443ed83eb388 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/services/documentation.ts
+++ b/x-pack/plugins/ingest_pipelines/public/application/services/documentation.ts
@@ -26,6 +26,10 @@ export class DocumentationService {
public getHandlingFailureUrl() {
return `${this.esDocBasePath}/handling-failure-in-pipelines.html`;
}
+
+ public getPutPipelineApiUrl() {
+ return `${this.esDocBasePath}/put-pipeline-api.html`;
+ }
}
export const documentationService = new DocumentationService();
diff --git a/x-pack/plugins/ingest_pipelines/public/shared_imports.ts b/x-pack/plugins/ingest_pipelines/public/shared_imports.ts
index 3067d06174ba7..1035a1d8fc864 100644
--- a/x-pack/plugins/ingest_pipelines/public/shared_imports.ts
+++ b/x-pack/plugins/ingest_pipelines/public/shared_imports.ts
@@ -25,6 +25,8 @@ export {
getUseField,
ValidationFuncArg,
FormData,
+ FormHook,
+ useFormContext,
} from '../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib';
export {
diff --git a/x-pack/plugins/ingest_pipelines/server/routes/api/create.ts b/x-pack/plugins/ingest_pipelines/server/routes/api/create.ts
index cad29a2fe555d..63637eaac765d 100644
--- a/x-pack/plugins/ingest_pipelines/server/routes/api/create.ts
+++ b/x-pack/plugins/ingest_pipelines/server/routes/api/create.ts
@@ -15,7 +15,7 @@ const bodySchema = schema.object({
description: schema.string(),
processors: schema.arrayOf(schema.recordOf(schema.string(), schema.any())),
version: schema.maybe(schema.number()),
- onFailure: schema.maybe(schema.arrayOf(schema.recordOf(schema.string(), schema.any()))),
+ on_failure: schema.maybe(schema.arrayOf(schema.recordOf(schema.string(), schema.any()))),
});
export const registerCreateRoute = ({
@@ -34,7 +34,7 @@ export const registerCreateRoute = ({
const { callAsCurrentUser } = ctx.core.elasticsearch.dataClient;
const pipeline = req.body as Pipeline;
- const { name, description, processors, version, onFailure } = pipeline;
+ const { name, description, processors, version, on_failure } = pipeline;
try {
// Check that a pipeline with the same name doesn't already exist
@@ -63,7 +63,7 @@ export const registerCreateRoute = ({
description,
processors,
version,
- on_failure: onFailure,
+ on_failure,
},
});
diff --git a/x-pack/plugins/ingest_pipelines/server/routes/api/get.ts b/x-pack/plugins/ingest_pipelines/server/routes/api/get.ts
index 3c39ac8a81b45..90ead800e5ddf 100644
--- a/x-pack/plugins/ingest_pipelines/server/routes/api/get.ts
+++ b/x-pack/plugins/ingest_pipelines/server/routes/api/get.ts
@@ -3,16 +3,22 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
+import { schema } from '@kbn/config-schema';
import { deserializePipelines } from '../../../common/lib';
import { API_BASE_PATH } from '../../../common/constants';
import { RouteDependencies } from '../../types';
+const paramsSchema = schema.object({
+ name: schema.string(),
+});
+
export const registerGetRoutes = ({
router,
license,
lib: { isEsError },
}: RouteDependencies): void => {
+ // Get all pipelines
router.get(
{ path: API_BASE_PATH, validate: false },
license.guardApiRoute(async (ctx, req, res) => {
@@ -34,4 +40,38 @@ export const registerGetRoutes = ({
}
})
);
+
+ // Get single pipeline
+ router.get(
+ {
+ path: `${API_BASE_PATH}/{name}`,
+ validate: {
+ params: paramsSchema,
+ },
+ },
+ license.guardApiRoute(async (ctx, req, res) => {
+ const { callAsCurrentUser } = ctx.core.elasticsearch.dataClient;
+ const { name } = req.params;
+
+ try {
+ const pipeline = await callAsCurrentUser('ingest.getPipeline', { id: name });
+
+ return res.ok({
+ body: {
+ ...pipeline[name],
+ name,
+ },
+ });
+ } catch (error) {
+ if (isEsError(error)) {
+ return res.customError({
+ statusCode: error.statusCode,
+ body: error,
+ });
+ }
+
+ return res.internalError({ body: error });
+ }
+ })
+ );
};
diff --git a/x-pack/plugins/ingest_pipelines/server/routes/api/update.ts b/x-pack/plugins/ingest_pipelines/server/routes/api/update.ts
index 4a13c3b15b754..27a3c9fb97ef8 100644
--- a/x-pack/plugins/ingest_pipelines/server/routes/api/update.ts
+++ b/x-pack/plugins/ingest_pipelines/server/routes/api/update.ts
@@ -13,7 +13,7 @@ const bodySchema = schema.object({
description: schema.string(),
processors: schema.arrayOf(schema.recordOf(schema.string(), schema.any())),
version: schema.maybe(schema.number()),
- onFailure: schema.maybe(schema.arrayOf(schema.recordOf(schema.string(), schema.any()))),
+ on_failure: schema.maybe(schema.arrayOf(schema.recordOf(schema.string(), schema.any()))),
});
const paramsSchema = schema.object({
@@ -38,7 +38,7 @@ export const registerUpdateRoute = ({
const { name } = req.params;
const pipeline = req.body as Pipeline;
- const { description, processors, version, onFailure } = pipeline;
+ const { description, processors, version, on_failure } = pipeline;
try {
// Verify pipeline exists; ES will throw 404 if it doesn't
@@ -50,7 +50,7 @@ export const registerUpdateRoute = ({
description,
processors,
version,
- on_failure: onFailure,
+ on_failure,
},
});
diff --git a/x-pack/test/api_integration/apis/management/ingest_pipelines/ingest_pipelines.ts b/x-pack/test/api_integration/apis/management/ingest_pipelines/ingest_pipelines.ts
index 7c5a97f715869..41f285938c003 100644
--- a/x-pack/test/api_integration/apis/management/ingest_pipelines/ingest_pipelines.ts
+++ b/x-pack/test/api_integration/apis/management/ingest_pipelines/ingest_pipelines.ts
@@ -35,7 +35,7 @@ export default function({ getService }: FtrProviderContext) {
},
},
],
- onFailure: [
+ on_failure: [
{
set: {
field: 'error.message',
@@ -131,5 +131,59 @@ export default function({ getService }: FtrProviderContext) {
});
});
});
+
+ describe('Get', () => {
+ const PIPELINE_ID = 'test_pipeline';
+ const PIPELINE = {
+ description: 'test pipeline description',
+ processors: [
+ {
+ script: {
+ source: 'ctx._type = null',
+ },
+ },
+ ],
+ version: 1,
+ };
+
+ before(() => createPipeline({ body: PIPELINE, id: PIPELINE_ID }));
+ after(() => deletePipeline(PIPELINE_ID));
+
+ describe('all pipelines', () => {
+ it('should return an array of pipelines', async () => {
+ const { body } = await supertest
+ .get(API_BASE_PATH)
+ .set('kbn-xsrf', 'xxx')
+ .expect(200);
+
+ expect(Array.isArray(body)).to.be(true);
+
+ // There are some pipelines created OOTB with ES
+ // To not be dependent on these, we only confirm the pipeline we created as part of the test exists
+ const testPipeline = body.find(({ name }: { name: string }) => name === PIPELINE_ID);
+
+ expect(testPipeline).to.eql({
+ ...PIPELINE,
+ name: PIPELINE_ID,
+ });
+ });
+ });
+
+ describe('one pipeline', () => {
+ it('should return a single pipeline', async () => {
+ const uri = `${API_BASE_PATH}/${PIPELINE_ID}`;
+
+ const { body } = await supertest
+ .get(uri)
+ .set('kbn-xsrf', 'xxx')
+ .expect(200);
+
+ expect(body).to.eql({
+ ...PIPELINE,
+ name: PIPELINE_ID,
+ });
+ });
+ });
+ });
});
}