Skip to content

Commit

Permalink
[7.x] Ingest Node Pipelines UI (#62321) (#64956)
Browse files Browse the repository at this point in the history
  • Loading branch information
alisonelizabeth authored May 5, 2020
1 parent e81ff9d commit 2525d57
Show file tree
Hide file tree
Showing 83 changed files with 4,560 additions and 0 deletions.
1 change: 1 addition & 0 deletions x-pack/.i18nrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"xpack.indexLifecycleMgmt": "plugins/index_lifecycle_management",
"xpack.infra": "plugins/infra",
"xpack.ingestManager": "plugins/ingest_manager",
"xpack.ingestPipelines": "plugins/ingest_pipelines",
"xpack.lens": "plugins/lens",
"xpack.licenseMgmt": "plugins/license_management",
"xpack.licensing": "plugins/licensing",
Expand Down
24 changes: 24 additions & 0 deletions x-pack/plugins/ingest_pipelines/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Ingest Node Pipelines UI

## Summary
The `ingest_pipelines` plugin provides Kibana support for [Elasticsearch's ingest nodes](https://www.elastic.co/guide/en/elasticsearch/reference/master/ingest.html). Please refer to the Elasticsearch documentation for more details.

This plugin allows Kibana to create, edit, clone and delete ingest node pipelines. It also provides support to simulate a pipeline.

It requires a Basic license and the following cluster privileges: `manage_pipeline` and `cluster:monitor/nodes/info`.

---

## Development

A new app called Ingest Node Pipelines is registered in the Management section and follows a typical CRUD UI pattern. The client-side portion of this app lives in [public/application](public/application) and uses endpoints registered in [server/routes/api](server/routes/api).

See the [kibana contributing guide](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md) for instructions on setting up your development environment.

### Test coverage

The app has the following test coverage:

- Complete API integration tests
- Smoke-level functional test
- Client-integration tests
18 changes: 18 additions & 0 deletions x-pack/plugins/ingest_pipelines/common/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* 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 { LicenseType } from '../../licensing/common/types';

const basicLicense: LicenseType = 'basic';

export const PLUGIN_ID = 'ingest_pipelines';

export const PLUGIN_MIN_LICENSE_TYPE = basicLicense;

export const BASE_PATH = '/management/elasticsearch/ingest_pipelines';

export const API_BASE_PATH = '/api/ingest_pipelines';

export const APP_CLUSTER_REQUIRED_PRIVILEGES = ['manage_pipeline', 'cluster:monitor/nodes/info'];
7 changes: 7 additions & 0 deletions x-pack/plugins/ingest_pipelines/common/lib/index.ts
Original file line number Diff line number Diff line change
@@ -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 { deserializePipelines } from './pipeline_serialization';
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* 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 { deserializePipelines } from './pipeline_serialization';

describe('pipeline_serialization', () => {
describe('deserializePipelines()', () => {
it('should deserialize pipelines', () => {
expect(
deserializePipelines({
pipeline1: {
description: 'pipeline 1 description',
version: 1,
processors: [
{
script: {
source: 'ctx._type = null',
},
},
],
on_failure: [
{
set: {
field: 'error.message',
value: '{{ failure_message }}',
},
},
],
},
pipeline2: {
description: 'pipeline2 description',
version: 1,
processors: [],
},
})
).toEqual([
{
name: 'pipeline1',
description: 'pipeline 1 description',
version: 1,
processors: [
{
script: {
source: 'ctx._type = null',
},
},
],
on_failure: [
{
set: {
field: 'error.message',
value: '{{ failure_message }}',
},
},
],
},
{
name: 'pipeline2',
description: 'pipeline2 description',
version: 1,
processors: [],
},
]);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* 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 { PipelinesByName, Pipeline } from '../types';

export function deserializePipelines(pipelinesByName: PipelinesByName): Pipeline[] {
const pipelineNames: string[] = Object.keys(pipelinesByName);

const deserializedPipelines = pipelineNames.map((name: string) => {
return {
...pipelinesByName[name],
name,
};
});

return deserializedPipelines;
}
28 changes: 28 additions & 0 deletions x-pack/plugins/ingest_pipelines/common/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* 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.
*/

interface Processor {
[key: string]: {
[key: string]: unknown;
};
}

export interface Pipeline {
name: string;
description: string;
version?: number;
processors: Processor[];
on_failure?: Processor[];
}

export interface PipelinesByName {
[key: string]: {
description: string;
version?: number;
processors: Processor[];
on_failure?: Processor[];
};
}
17 changes: 17 additions & 0 deletions x-pack/plugins/ingest_pipelines/kibana.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"id": "ingestPipelines",
"version": "kibana",
"server": true,
"ui": true,
"requiredPlugins": [
"licensing",
"management"
],
"optionalPlugins": [
"usageCollection"
],
"configPath": [
"xpack",
"ingest_pipelines"
]
}
101 changes: 101 additions & 0 deletions x-pack/plugins/ingest_pipelines/public/application/app.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* 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 { EuiPageContent } from '@elastic/eui';
import React, { FunctionComponent } from 'react';
import { HashRouter, Switch, Route } from 'react-router-dom';

import { BASE_PATH, APP_CLUSTER_REQUIRED_PRIVILEGES } from '../../common/constants';

import {
SectionError,
useAuthorizationContext,
WithPrivileges,
SectionLoading,
NotAuthorizedSection,
} from '../shared_imports';

import { PipelinesList, PipelinesCreate, PipelinesEdit, PipelinesClone } from './sections';

export const AppWithoutRouter = () => (
<Switch>
<Route exact path={BASE_PATH} component={PipelinesList} />
<Route exact path={`${BASE_PATH}/create/:sourceName`} component={PipelinesClone} />
<Route exact path={`${BASE_PATH}/create`} component={PipelinesCreate} />
<Route exact path={`${BASE_PATH}/edit/:name`} component={PipelinesEdit} />
{/* Catch all */}
<Route component={PipelinesList} />
</Switch>
);

export const App: FunctionComponent = () => {
const { apiError } = useAuthorizationContext();

if (apiError) {
return (
<SectionError
title={
<FormattedMessage
id="xpack.ingestPipelines.app.checkingPrivilegesErrorMessage"
defaultMessage="Error fetching user privileges from the server."
/>
}
error={apiError}
/>
);
}

return (
<WithPrivileges
privileges={APP_CLUSTER_REQUIRED_PRIVILEGES.map(privilege => `cluster.${privilege}`)}
>
{({ isLoading, hasPrivileges, privilegesMissing }) => {
if (isLoading) {
return (
<SectionLoading>
<FormattedMessage
id="xpack.ingestPipelines.app.checkingPrivilegesDescription"
defaultMessage="Checking privileges…"
/>
</SectionLoading>
);
}

if (!hasPrivileges) {
return (
<EuiPageContent>
<NotAuthorizedSection
title={
<FormattedMessage
id="xpack.ingestPipelines.app.deniedPrivilegeTitle"
defaultMessage="You're missing cluster privileges"
/>
}
message={
<FormattedMessage
id="xpack.ingestPipelines.app.deniedPrivilegeDescription"
defaultMessage="To use Ingest Pipelines, you must have {privilegesCount,
plural, one {this cluster privilege} other {these cluster privileges}}: {missingPrivileges}."
values={{
missingPrivileges: privilegesMissing.cluster!.join(', '),
privilegesCount: privilegesMissing.cluster!.length,
}}
/>
}
/>
</EuiPageContent>
);
}

return (
<HashRouter>
<AppWithoutRouter />
</HashRouter>
);
}}
</WithPrivileges>
);
};
Original file line number Diff line number Diff line change
@@ -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 { PipelineForm } from './pipeline_form';
Original file line number Diff line number Diff line change
@@ -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 { PipelineFormProvider as PipelineForm } from './pipeline_form_provider';
Loading

0 comments on commit 2525d57

Please sign in to comment.