Skip to content

Commit a04a980

Browse files
committed
Merge branch 'feature/ingest-node-pipelines' of github.com:elastic/kibana into ingest-node-pipelines/privileges
* 'feature/ingest-node-pipelines' of github.com:elastic/kibana: [Ingest Node Pipelines] Clone Pipeline (#64049) # Conflicts: # x-pack/plugins/ingest_pipelines/public/application/app.tsx
2 parents 98b16e1 + 34cb91a commit a04a980

File tree

8 files changed

+194
-16
lines changed

8 files changed

+194
-16
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
@@ -18,11 +18,12 @@ import {
1818
NotAuthorizedSection,
1919
} from '../shared_imports';
2020

21-
import { PipelinesList, PipelinesCreate, PipelinesEdit } from './sections';
21+
import { PipelinesList, PipelinesCreate, PipelinesEdit, PipelinesClone } from './sections';
2222

2323
export const AppWithoutRouter = () => (
2424
<Switch>
2525
<Route exact path={BASE_PATH} component={PipelinesList} />
26+
<Route exact path={`${BASE_PATH}/create/:sourceName`} component={PipelinesClone} />
2627
<Route exact path={`${BASE_PATH}/create`} component={PipelinesCreate} />
2728
<Route exact path={`${BASE_PATH}/edit/:name`} component={PipelinesEdit} />
2829
</Switch>

x-pack/plugins/ingest_pipelines/public/application/sections/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,5 @@ export { PipelinesList } from './pipelines_list';
99
export { PipelinesCreate } from './pipelines_create';
1010

1111
export { PipelinesEdit } from './pipelines_edit';
12+
13+
export { PipelinesClone } from './pipelines_clone';
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 { PipelinesClone } from './pipelines_clone';
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
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+
import React, { FunctionComponent, useEffect } from 'react';
8+
import { RouteComponentProps } from 'react-router-dom';
9+
import { i18n } from '@kbn/i18n';
10+
import { FormattedMessage } from '@kbn/i18n/react';
11+
12+
import { SectionLoading, useKibana } from '../../../shared_imports';
13+
14+
import { PipelinesCreate } from '../pipelines_create';
15+
16+
export interface ParamProps {
17+
sourceName: string;
18+
}
19+
20+
/**
21+
* This section is a wrapper around the create section where we receive a pipeline name
22+
* to load and set as the source pipeline for the {@link PipelinesCreate} form.
23+
*/
24+
export const PipelinesClone: FunctionComponent<RouteComponentProps<ParamProps>> = props => {
25+
const { sourceName } = props.match.params;
26+
const { services } = useKibana();
27+
28+
const { error, data: pipeline, isLoading, isInitialRequest } = services.api.useLoadPipeline(
29+
decodeURIComponent(sourceName)
30+
);
31+
32+
useEffect(() => {
33+
if (error && !isLoading) {
34+
services.notifications!.toasts.addError(error, {
35+
title: i18n.translate('xpack.ingestPipelines.clone.loadSourcePipelineErrorTitle', {
36+
defaultMessage: 'Cannot load {name}.',
37+
values: { name: sourceName },
38+
}),
39+
});
40+
}
41+
// eslint-disable-next-line react-hooks/exhaustive-deps
42+
}, [error, isLoading]);
43+
44+
if (isLoading && isInitialRequest) {
45+
return (
46+
<SectionLoading>
47+
<FormattedMessage
48+
id="xpack.ingestPipelines.clone.loadingPipelinesDescription"
49+
defaultMessage="Loading pipeline…"
50+
/>
51+
</SectionLoading>
52+
);
53+
} else {
54+
// We still show the create form even if we were not able to load the
55+
// latest pipeline data.
56+
const sourcePipeline = pipeline ? { ...pipeline, name: `${pipeline.name}-copy` } : undefined;
57+
return <PipelinesCreate {...props} sourcePipeline={sourcePipeline} />;
58+
}
59+
};

x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_create/pipelines_create.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,17 @@ import { Pipeline } from '../../../../common/types';
2121
import { useKibana } from '../../../shared_imports';
2222
import { PipelineForm } from '../../components';
2323

24-
export const PipelinesCreate: React.FunctionComponent<RouteComponentProps> = ({ history }) => {
24+
interface Props {
25+
/**
26+
* This value may be passed in to prepopulate the creation form
27+
*/
28+
sourcePipeline?: Pipeline;
29+
}
30+
31+
export const PipelinesCreate: React.FunctionComponent<RouteComponentProps & Props> = ({
32+
history,
33+
sourcePipeline,
34+
}) => {
2535
const { services } = useKibana();
2636

2737
const [isSaving, setIsSaving] = useState<boolean>(false);
@@ -87,6 +97,7 @@ export const PipelinesCreate: React.FunctionComponent<RouteComponentProps> = ({
8797
<EuiSpacer size="l" />
8898

8999
<PipelineForm
100+
defaultValue={sourcePipeline}
90101
onSave={onSave}
91102
onCancel={onCancel}
92103
isSaving={isSaving}

x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/details.tsx

Lines changed: 87 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* you may not use this file except in compliance with the Elastic License.
55
*/
66

7-
import React, { FunctionComponent } from 'react';
7+
import React, { FunctionComponent, useState } from 'react';
88
import { i18n } from '@kbn/i18n';
99
import {
1010
EuiFlyout,
@@ -18,14 +18,20 @@ import {
1818
EuiFlexGroup,
1919
EuiFlexItem,
2020
EuiButtonEmpty,
21+
EuiIcon,
22+
EuiPopover,
23+
EuiContextMenu,
24+
EuiButton,
2125
} from '@elastic/eui';
26+
2227
import { Pipeline } from '../../../../common/types';
2328

2429
import { PipelineDetailsJsonBlock } from './details_json_block';
2530

2631
export interface Props {
2732
pipeline: Pipeline;
2833
onEditClick: (pipelineName: string) => void;
34+
onCloneClick: (pipelineName: string) => void;
2935
onDeleteClick: (pipelineName: string[]) => void;
3036
onClose: () => void;
3137
}
@@ -34,8 +40,63 @@ export const PipelineDetails: FunctionComponent<Props> = ({
3440
pipeline,
3541
onClose,
3642
onEditClick,
43+
onCloneClick,
3744
onDeleteClick,
3845
}) => {
46+
const [showPopover, setShowPopover] = useState(false);
47+
const actionMenuItems = [
48+
/**
49+
* Edit pipeline
50+
*/
51+
{
52+
name: i18n.translate('xpack.ingestPipelines.list.pipelineDetails.editActionLabel', {
53+
defaultMessage: 'Edit',
54+
}),
55+
icon: <EuiIcon type="pencil" />,
56+
onClick: () => onEditClick(pipeline.name),
57+
},
58+
/**
59+
* Clone pipeline
60+
*/
61+
{
62+
name: i18n.translate('xpack.ingestPipelines.list.pipelineDetails.cloneActionLabel', {
63+
defaultMessage: 'Clone',
64+
}),
65+
icon: <EuiIcon type="copy" />,
66+
onClick: () => onCloneClick(pipeline.name),
67+
},
68+
/**
69+
* Delete pipeline
70+
*/
71+
{
72+
name: i18n.translate('xpack.ingestPipelines.list.pipelineDetails.deleteActionLabel', {
73+
defaultMessage: 'Delete',
74+
}),
75+
icon: <EuiIcon type="trash" />,
76+
onClick: () => onDeleteClick([pipeline.name]),
77+
},
78+
];
79+
80+
const managePipelineButton = (
81+
<EuiButton
82+
data-test-subj="managePipelineButton"
83+
aria-label={i18n.translate(
84+
'xpack.ingestPipelines.list.pipelineDetails.managePipelineActionsAriaLabel',
85+
{
86+
defaultMessage: 'Manage pipeline',
87+
}
88+
)}
89+
onClick={() => setShowPopover(previousBool => !previousBool)}
90+
iconType="arrowUp"
91+
iconSide="right"
92+
fill
93+
>
94+
{i18n.translate('xpack.ingestPipelines.list.pipelineDetails.managePipelineButtonLabel', {
95+
defaultMessage: 'Manage',
96+
})}
97+
</EuiButton>
98+
);
99+
39100
return (
40101
<EuiFlyout
41102
onClose={onClose}
@@ -115,18 +176,31 @@ export const PipelineDetails: FunctionComponent<Props> = ({
115176
</EuiFlexItem>
116177
<EuiFlexGroup gutterSize="none" alignItems="center" justifyContent="flexEnd">
117178
<EuiFlexItem grow={false}>
118-
<EuiButtonEmpty onClick={() => onEditClick(pipeline.name)}>
119-
{i18n.translate('xpack.ingestPipelines.list.pipelineDetails.editButtonLabel', {
120-
defaultMessage: 'Edit',
121-
})}
122-
</EuiButtonEmpty>
123-
</EuiFlexItem>
124-
<EuiFlexItem grow={false}>
125-
<EuiButtonEmpty color="danger" onClick={() => onDeleteClick([pipeline.name])}>
126-
{i18n.translate('xpack.ingestPipelines.list.pipelineDetails.deleteButtonLabel', {
127-
defaultMessage: 'Delete',
128-
})}
129-
</EuiButtonEmpty>
179+
<EuiPopover
180+
isOpen={showPopover}
181+
closePopover={() => setShowPopover(false)}
182+
button={managePipelineButton}
183+
panelPaddingSize="none"
184+
withTitle
185+
repositionOnScroll
186+
>
187+
<EuiContextMenu
188+
initialPanelId={0}
189+
data-test-subj="autoFollowPatternActionContextMenu"
190+
panels={[
191+
{
192+
id: 0,
193+
title: i18n.translate(
194+
'xpack.ingestPipelines.list.pipelineDetails.managePipelinePanelTitle',
195+
{
196+
defaultMessage: 'Pipeline options',
197+
}
198+
),
199+
items: actionMenuItems,
200+
},
201+
]}
202+
/>
203+
</EuiPopover>
130204
</EuiFlexItem>
131205
</EuiFlexGroup>
132206
</EuiFlexGroup>

x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/main.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ export const PipelinesList: React.FunctionComponent<RouteComponentProps> = ({ hi
5151
history.push(encodeURI(`${BASE_PATH}/edit/${encodeURIComponent(name)}`));
5252
};
5353

54+
const clonePipeline = (name: string) => {
55+
history.push(encodeURI(`${BASE_PATH}/create/${encodeURIComponent(name)}`));
56+
};
57+
5458
if (isLoading) {
5559
content = (
5660
<SectionLoading>
@@ -66,6 +70,7 @@ export const PipelinesList: React.FunctionComponent<RouteComponentProps> = ({ hi
6670
onReloadClick={sendRequest}
6771
onEditPipelineClick={editPipeline}
6872
onDeletePipelineClick={setPipelinesToDelete}
73+
onClonePipelineClick={clonePipeline}
6974
onViewPipelineClick={setSelectedPipeline}
7075
pipelines={data}
7176
/>
@@ -130,8 +135,9 @@ export const PipelinesList: React.FunctionComponent<RouteComponentProps> = ({ hi
130135
<PipelineDetails
131136
pipeline={selectedPipeline}
132137
onClose={() => setSelectedPipeline(undefined)}
133-
onDeleteClick={setPipelinesToDelete}
134138
onEditClick={editPipeline}
139+
onCloneClick={clonePipeline}
140+
onDeleteClick={setPipelinesToDelete}
135141
/>
136142
)}
137143
{pipelinesToDelete?.length > 0 ? (

x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/table.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export interface Props {
1515
pipelines: Pipeline[];
1616
onReloadClick: () => void;
1717
onEditPipelineClick: (pipelineName: string) => void;
18+
onClonePipelineClick: (pipelineName: string) => void;
1819
onDeletePipelineClick: (pipelineName: string[]) => void;
1920
onViewPipelineClick: (pipeline: Pipeline) => void;
2021
}
@@ -23,6 +24,7 @@ export const PipelineTable: FunctionComponent<Props> = ({
2324
pipelines,
2425
onReloadClick,
2526
onEditPipelineClick,
27+
onClonePipelineClick,
2628
onDeletePipelineClick,
2729
onViewPipelineClick,
2830
}) => {
@@ -32,6 +34,7 @@ export const PipelineTable: FunctionComponent<Props> = ({
3234
<EuiInMemoryTable
3335
itemId="name"
3436
isSelectable
37+
sorting={{ sort: { field: 'name', direction: 'asc' } }}
3538
selection={{
3639
onSelectionChange: setSelection,
3740
}}
@@ -90,6 +93,7 @@ export const PipelineTable: FunctionComponent<Props> = ({
9093
name: i18n.translate('xpack.ingestPipelines.list.table.nameColumnTitle', {
9194
defaultMessage: 'Name',
9295
}),
96+
sortable: true,
9397
render: (name: string, pipeline) => (
9498
<EuiLink onClick={() => onViewPipelineClick(pipeline)}>{name}</EuiLink>
9599
),
@@ -100,6 +104,7 @@ export const PipelineTable: FunctionComponent<Props> = ({
100104
}),
101105
actions: [
102106
{
107+
isPrimary: true,
103108
name: i18n.translate('xpack.ingestPipelines.list.table.editActionLabel', {
104109
defaultMessage: 'Edit',
105110
}),
@@ -112,6 +117,19 @@ export const PipelineTable: FunctionComponent<Props> = ({
112117
onClick: ({ name }) => onEditPipelineClick(name),
113118
},
114119
{
120+
name: i18n.translate('xpack.ingestPipelines.list.table.cloneActionLabel', {
121+
defaultMessage: 'Clone',
122+
}),
123+
description: i18n.translate(
124+
'xpack.ingestPipelines.list.table.cloneActionDescription',
125+
{ defaultMessage: 'Clone this pipeline' }
126+
),
127+
type: 'icon',
128+
icon: 'copy',
129+
onClick: ({ name }) => onClonePipelineClick(name),
130+
},
131+
{
132+
isPrimary: true,
115133
name: i18n.translate('xpack.ingestPipelines.list.table.deleteActionLabel', {
116134
defaultMessage: 'Delete',
117135
}),

0 commit comments

Comments
 (0)