Skip to content

Commit 92b3f21

Browse files
[Ingest pipelines] Create pipeline UI (#63017)
1 parent 699ebef commit 92b3f21

File tree

23 files changed

+714
-33
lines changed

23 files changed

+714
-33
lines changed

x-pack/plugins/ingest_pipelines/public/application/app.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import React from 'react';
88
import { HashRouter, Switch, Route } from 'react-router-dom';
99
import { BASE_PATH } from '../../common/constants';
10-
import { PipelinesList } from './sections';
10+
import { PipelinesList, PipelinesCreate } from './sections';
1111

1212
export const App = () => {
1313
return (
@@ -20,5 +20,6 @@ export const App = () => {
2020
export const AppWithoutRouter = () => (
2121
<Switch>
2222
<Route exact path={BASE_PATH} component={PipelinesList} />
23+
<Route exact path={`${BASE_PATH}/create`} component={PipelinesCreate} />
2324
</Switch>
2425
);
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
export { PipelineForm } from './pipeline_form';
8+
9+
export { SectionError } from './section_error';
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
export { PipelineForm } from './pipeline_form';
Lines changed: 294 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,294 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
import React, { useState } from 'react';
7+
import { FormattedMessage } from '@kbn/i18n/react';
8+
import { i18n } from '@kbn/i18n';
9+
import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiSwitch, EuiLink } from '@elastic/eui';
10+
11+
import {
12+
useForm,
13+
Form,
14+
getUseField,
15+
getFormRow,
16+
Field,
17+
FormConfig,
18+
JsonEditorField,
19+
useKibana,
20+
} from '../../../shared_imports';
21+
import { Pipeline } from '../../../../common/types';
22+
23+
import { SectionError } from '../section_error';
24+
import { pipelineFormSchema } from './schema';
25+
26+
interface Props {
27+
onSave: (pipeline: Pipeline) => void;
28+
isSaving: boolean;
29+
saveError: any;
30+
defaultValue?: Pipeline;
31+
}
32+
33+
const UseField = getUseField({ component: Field });
34+
const FormRow = getFormRow({ titleTag: 'h3' });
35+
36+
export const PipelineForm: React.FunctionComponent<Props> = ({
37+
defaultValue = {
38+
name: '',
39+
description: '',
40+
processors: '',
41+
onFailure: '',
42+
version: '',
43+
},
44+
onSave,
45+
isSaving,
46+
saveError,
47+
}) => {
48+
const { services } = useKibana();
49+
50+
const [isVersionVisible, setIsVersionVisible] = useState<boolean>(false);
51+
const [isOnFailureEditorVisible, setIsOnFailureEditorVisible] = useState<boolean>(false);
52+
53+
const handleSave: FormConfig['onSubmit'] = (formData, isValid) => {
54+
if (isValid) {
55+
onSave(formData as Pipeline);
56+
}
57+
};
58+
59+
const { form } = useForm({
60+
schema: pipelineFormSchema,
61+
defaultValue,
62+
onSubmit: handleSave,
63+
});
64+
65+
return (
66+
<>
67+
{saveError ? (
68+
<>
69+
<SectionError
70+
title={
71+
<FormattedMessage
72+
id="xpack.ingestPipelines.form.savePipelineError"
73+
defaultMessage="Unable to create pipeline"
74+
/>
75+
}
76+
error={saveError}
77+
data-test-subj="savePipelineError"
78+
/>
79+
<EuiSpacer size="m" />
80+
</>
81+
) : null}
82+
83+
<Form
84+
form={form}
85+
data-test-subj="pipelineForm"
86+
isInvalid={form.isSubmitted && !form.isValid}
87+
error={form.getErrors()}
88+
>
89+
{/* Name field with optional version field */}
90+
<FormRow
91+
title={
92+
<FormattedMessage id="xpack.ingestPipelines.form.nameTitle" defaultMessage="Name" />
93+
}
94+
description={
95+
<>
96+
<FormattedMessage
97+
id="xpack.ingestPipelines.form.nameDescription"
98+
defaultMessage="A unique identifier for this pipeline."
99+
/>
100+
<EuiSpacer size="m" />
101+
<EuiSwitch
102+
label={
103+
<FormattedMessage
104+
id="xpack.ingestPipelines.form.versionToggleDescription"
105+
defaultMessage="Add version number"
106+
/>
107+
}
108+
checked={isVersionVisible}
109+
onChange={e => setIsVersionVisible(e.target.checked)}
110+
data-test-subj="versionToggle"
111+
/>
112+
</>
113+
}
114+
>
115+
<UseField
116+
path="name"
117+
componentProps={{
118+
['data-test-subj']: 'nameField',
119+
}}
120+
/>
121+
122+
{isVersionVisible && (
123+
<UseField
124+
path="version"
125+
componentProps={{
126+
['data-test-subj']: 'versionField',
127+
}}
128+
/>
129+
)}
130+
</FormRow>
131+
132+
{/* Description */}
133+
<FormRow
134+
title={
135+
<FormattedMessage
136+
id="xpack.ingestPipelines.form.descriptionFieldTitle"
137+
defaultMessage="Description"
138+
/>
139+
}
140+
description={
141+
<FormattedMessage
142+
id="xpack.ingestPipelines.form.descriptionFieldDescription"
143+
defaultMessage="The description to apply to the pipeline."
144+
/>
145+
}
146+
>
147+
<UseField
148+
path="description"
149+
componentProps={{
150+
['data-test-subj']: 'descriptionField',
151+
euiFieldProps: {
152+
compressed: true,
153+
},
154+
}}
155+
/>
156+
</FormRow>
157+
158+
{/* Processors field */}
159+
<FormRow
160+
title={
161+
<FormattedMessage
162+
id="xpack.ingestPipelines.form.processorsFieldTitle"
163+
defaultMessage="Processors"
164+
/>
165+
}
166+
description={
167+
<FormattedMessage
168+
id="xpack.ingestPipelines.form.processorsFieldDescription"
169+
defaultMessage="The processors used to pre-process documents before indexing. {learnMoreLink}"
170+
values={{
171+
learnMoreLink: (
172+
<EuiLink href={services.documentation.getProcessorsUrl()} target="_blank">
173+
{i18n.translate('xpack.ingestPipelines.form.processorsDocumentionLink', {
174+
defaultMessage: 'Learn more.',
175+
})}
176+
</EuiLink>
177+
),
178+
}}
179+
/>
180+
}
181+
>
182+
<UseField
183+
path="processors"
184+
component={JsonEditorField}
185+
componentProps={{
186+
['data-test-subj']: 'processorsField',
187+
euiCodeEditorProps: {
188+
height: '300px',
189+
'aria-label': i18n.translate(
190+
'xpack.ingestPipelines.form.processorsFieldAriaLabel',
191+
{
192+
defaultMessage: 'Processors JSON editor',
193+
}
194+
),
195+
},
196+
}}
197+
/>
198+
</FormRow>
199+
200+
{/* On-failure field */}
201+
<FormRow
202+
title={
203+
<FormattedMessage
204+
id="xpack.ingestPipelines.form.onFailureTitle"
205+
defaultMessage="Failure processors"
206+
/>
207+
}
208+
description={
209+
<>
210+
<FormattedMessage
211+
id="xpack.ingestPipelines.form.onFailureDescription"
212+
defaultMessage="The processors to be executed following a failed processor. {learnMoreLink}"
213+
values={{
214+
learnMoreLink: (
215+
<EuiLink href={services.documentation.getHandlingFailureUrl()} target="_blank">
216+
{i18n.translate('xpack.ingestPipelines.form.onFailureDocumentionLink', {
217+
defaultMessage: 'Learn more.',
218+
})}
219+
</EuiLink>
220+
),
221+
}}
222+
/>
223+
<EuiSpacer size="m" />
224+
<EuiSwitch
225+
label={
226+
<FormattedMessage
227+
id="xpack.ingestPipelines.form.onFailureToggleDescription"
228+
defaultMessage="Add on-failure processors"
229+
/>
230+
}
231+
checked={isOnFailureEditorVisible}
232+
onChange={e => setIsOnFailureEditorVisible(e.target.checked)}
233+
data-test-subj="onFailureToggle"
234+
/>
235+
</>
236+
}
237+
>
238+
{isOnFailureEditorVisible ? (
239+
<UseField
240+
path="onFailure"
241+
component={JsonEditorField}
242+
componentProps={{
243+
['data-test-subj']: 'onFailureEditor',
244+
euiCodeEditorProps: {
245+
height: '300px',
246+
'aria-label': i18n.translate(
247+
'xpack.ingestPipelines.form.onFailureFieldAriaLabel',
248+
{
249+
defaultMessage: 'On-failure processors JSON editor',
250+
}
251+
),
252+
},
253+
}}
254+
/>
255+
) : (
256+
// <FormRow/> requires children or a field
257+
// For now, we return an empty <div> if the editor is not visible
258+
<div />
259+
)}
260+
</FormRow>
261+
262+
<EuiSpacer size="l" />
263+
264+
{/* Form submission */}
265+
<EuiFlexGroup justifyContent="spaceBetween">
266+
<EuiFlexItem grow={false}>
267+
<EuiFlexGroup>
268+
<EuiFlexItem grow={false}>
269+
<EuiButton
270+
fill
271+
color="secondary"
272+
iconType="check"
273+
onClick={form.submit}
274+
data-test-subj="submitButton"
275+
disabled={form.isSubmitted && form.isValid === false}
276+
isLoading={isSaving}
277+
>
278+
{
279+
<FormattedMessage
280+
id="xpack.ingestPipelines.form.createButtonLabel"
281+
defaultMessage="Create pipeline"
282+
/>
283+
}
284+
</EuiButton>
285+
</EuiFlexItem>
286+
</EuiFlexGroup>
287+
</EuiFlexItem>
288+
</EuiFlexGroup>
289+
</Form>
290+
291+
<EuiSpacer size="m" />
292+
</>
293+
);
294+
};

0 commit comments

Comments
 (0)