From 5082ff3252f60f2ed6f4ed3789a1f142e1667e1f Mon Sep 17 00:00:00 2001
From: Mikhail Shustov
-
+
+
+
+
-
+
-
-
-
+
+
-
-
+export const AlphaMessaging: React.FC<{}> = () => {
+ const [isAlphaFlyoutOpen, setIsAlphaFlyoutOpen] = useState
+
+
+
+
+ {updateAvailable && (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
IIndexPattern | undefined
| |
| timeRange | TimeRange
| |
-| forceNow | Date
| |
+| options | {
forceNow?: Date;
fieldName?: string;
}
| |
Returns:
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpattern.gettimefield.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpattern.gettimefield.md
new file mode 100644
index 0000000000000..c3998876c9712
--- /dev/null
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpattern.gettimefield.md
@@ -0,0 +1,15 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IIndexPattern](./kibana-plugin-plugins-data-public.iindexpattern.md) > [getTimeField](./kibana-plugin-plugins-data-public.iindexpattern.gettimefield.md)
+
+## IIndexPattern.getTimeField() method
+
+Signature:
+
+```typescript
+getTimeField?(): IFieldType | undefined;
+```
+Returns:
+
+`IFieldType | undefined`
+
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpattern.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpattern.md
index 1bbd6cf67f0ce..1cb89822eb605 100644
--- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpattern.md
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpattern.md
@@ -21,3 +21,9 @@ export interface IIndexPattern
| [title](./kibana-plugin-plugins-data-public.iindexpattern.title.md) | string
| |
| [type](./kibana-plugin-plugins-data-public.iindexpattern.type.md) | string
| |
+## Methods
+
+| Method | Description |
+| --- | --- |
+| [getTimeField()](./kibana-plugin-plugins-data-public.iindexpattern.gettimefield.md) | |
+
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md
index 0fd82ffb2240c..e1df493143b73 100644
--- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md
@@ -43,7 +43,7 @@
| [getEsPreference(uiSettings, sessionId)](./kibana-plugin-plugins-data-public.getespreference.md) | |
| [getQueryLog(uiSettings, storage, appName, language)](./kibana-plugin-plugins-data-public.getquerylog.md) | |
| [getSearchErrorType({ message })](./kibana-plugin-plugins-data-public.getsearcherrortype.md) | |
-| [getTime(indexPattern, timeRange, forceNow)](./kibana-plugin-plugins-data-public.gettime.md) | |
+| [getTime(indexPattern, timeRange, options)](./kibana-plugin-plugins-data-public.gettime.md) | |
| [plugin(initializerContext)](./kibana-plugin-plugins-data-public.plugin.md) | |
## Interfaces
diff --git a/src/plugins/data/common/index_patterns/types.ts b/src/plugins/data/common/index_patterns/types.ts
index 698edbf9cd6a8..e21d27a70e02a 100644
--- a/src/plugins/data/common/index_patterns/types.ts
+++ b/src/plugins/data/common/index_patterns/types.ts
@@ -26,6 +26,7 @@ export interface IIndexPattern {
id?: string;
type?: string;
timeFieldName?: string;
+ getTimeField?(): IFieldType | undefined;
fieldFormatMap?: Record<
string,
{
diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md
index 86560b3ccf7b1..91dea66f06a94 100644
--- a/src/plugins/data/public/public.api.md
+++ b/src/plugins/data/public/public.api.md
@@ -699,7 +699,10 @@ export function getSearchErrorType({ message }: PickPartial<Capabilities>
| Custom capabilities defined by the app. |
| [category](./kibana-plugin-core-public.appbase.category.md) | AppCategory
| The category definition of the product See [AppCategory](./kibana-plugin-core-public.appcategory.md) See DEFAULT\_APP\_CATEGORIES for more reference |
| [chromeless](./kibana-plugin-core-public.appbase.chromeless.md) | boolean
| Hide the UI chrome when the application is mounted. Defaults to false
. Takes precedence over chrome service visibility settings. |
+| [defaultPath](./kibana-plugin-core-public.appbase.defaultpath.md) | string
| Allow to define the default path a user should be directed to when navigating to the app. When defined, this value will be used as a default for the path
option when calling [navigateToApp](./kibana-plugin-core-public.applicationstart.navigatetoapp.md)\`, and will also be appended to the [application navLink](./kibana-plugin-core-public.chromenavlink.md) in the navigation bar. |
| [euiIconType](./kibana-plugin-core-public.appbase.euiicontype.md) | string
| A EUI iconType that will be used for the app's icon. This icon takes precendence over the icon
property. |
| [icon](./kibana-plugin-core-public.appbase.icon.md) | string
| A URL to an image file used as an icon. Used as a fallback if euiIconType
is not provided. |
| [id](./kibana-plugin-core-public.appbase.id.md) | string
| The unique identifier of the application |
diff --git a/docs/development/core/public/kibana-plugin-core-public.appupdatablefields.md b/docs/development/core/public/kibana-plugin-core-public.appupdatablefields.md
index cdf9171a46aed..3d8b5d115c8a2 100644
--- a/docs/development/core/public/kibana-plugin-core-public.appupdatablefields.md
+++ b/docs/development/core/public/kibana-plugin-core-public.appupdatablefields.md
@@ -9,5 +9,5 @@ Defines the list of fields that can be updated via an [AppUpdater](./kibana-plug
Signature:
```typescript
-export declare type AppUpdatableFields = Pickstring
| A url base that legacy apps can set to match deep URLs to an application. |
| [title](./kibana-plugin-core-public.chromenavlink.title.md) | string
| The title of the application. |
| [tooltip](./kibana-plugin-core-public.chromenavlink.tooltip.md) | string
| A tooltip shown when hovering over an app link. |
-| [url](./kibana-plugin-core-public.chromenavlink.url.md) | string
| A url that legacy apps can set to deep link into their applications. |
+| [url](./kibana-plugin-core-public.chromenavlink.url.md) | string
| The route used to open the [default path](./kibana-plugin-core-public.appbase.defaultpath.md) of an application. If unset, baseUrl
will be used instead. |
diff --git a/docs/development/core/public/kibana-plugin-core-public.chromenavlink.url.md b/docs/development/core/public/kibana-plugin-core-public.chromenavlink.url.md
index 0c415ed1a7fad..1e0b890015993 100644
--- a/docs/development/core/public/kibana-plugin-core-public.chromenavlink.url.md
+++ b/docs/development/core/public/kibana-plugin-core-public.chromenavlink.url.md
@@ -4,11 +4,7 @@
## ChromeNavLink.url property
-> Warning: This API is now obsolete.
->
->
-
-A url that legacy apps can set to deep link into their applications.
+The route used to open the [default path](./kibana-plugin-core-public.appbase.defaultpath.md) of an application. If unset, `baseUrl` will be used instead.
Signature:
diff --git a/src/core/public/application/application_service.test.ts b/src/core/public/application/application_service.test.ts
index c25918c6b7328..e29837aecb125 100644
--- a/src/core/public/application/application_service.test.ts
+++ b/src/core/public/application/application_service.test.ts
@@ -87,7 +87,7 @@ describe('#setup()', () => {
).toThrowErrorMatchingInlineSnapshot(`"Applications cannot be registered after \\"setup\\""`);
});
- it('allows to register a statusUpdater for the application', async () => {
+ it('allows to register an AppUpdater for the application', async () => {
const setup = service.setup(setupDeps);
const pluginId = Symbol('plugin');
@@ -118,6 +118,7 @@ describe('#setup()', () => {
updater$.next(app => ({
status: AppStatus.inaccessible,
tooltip: 'App inaccessible due to reason',
+ defaultPath: 'foo/bar',
}));
applications = await applications$.pipe(take(1)).toPromise();
@@ -128,6 +129,7 @@ describe('#setup()', () => {
legacy: false,
navLinkStatus: AppNavLinkStatus.default,
status: AppStatus.inaccessible,
+ defaultPath: 'foo/bar',
tooltip: 'App inaccessible due to reason',
})
);
@@ -209,7 +211,7 @@ describe('#setup()', () => {
});
});
- describe('registerAppStatusUpdater', () => {
+ describe('registerAppUpdater', () => {
it('updates status fields', async () => {
const setup = service.setup(setupDeps);
@@ -413,6 +415,36 @@ describe('#setup()', () => {
})
);
});
+
+ it('allows to update the basePath', async () => {
+ const setup = service.setup(setupDeps);
+
+ const pluginId = Symbol('plugin');
+ setup.register(pluginId, createApp({ id: 'app1' }));
+
+ const updater = new BehaviorSubject{errorMessage}
+ +
+
{executeError.message}
+ +
+
+
{error.message}
+ +
+
(handler: RequestHandler
) { + const license = this; + + return function licenseCheck( + ctx: RequestHandlerContext, + request: KibanaRequest
,
+ response: KibanaResponseFactory
+ ) {
+ const licenseStatus = license.getStatus();
+
+ if (!licenseStatus.isValid) {
+ return response.customError({
+ body: {
+ message: licenseStatus.message || '',
+ },
+ statusCode: 403,
+ });
+ }
+
+ return handler(ctx, request, response);
+ };
+ }
+
+ getStatus() {
+ return this.licenseStatus;
+ }
+}
diff --git a/x-pack/plugins/ingest_pipelines/server/types.ts b/x-pack/plugins/ingest_pipelines/server/types.ts
new file mode 100644
index 0000000000000..0135ae8e2f07d
--- /dev/null
+++ b/x-pack/plugins/ingest_pipelines/server/types.ts
@@ -0,0 +1,22 @@
+/*
+ * 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 { IRouter } from 'src/core/server';
+import { LicensingPluginSetup } from '../../licensing/server';
+import { License } from './services';
+import { isEsError } from './lib';
+
+export interface Dependencies {
+ licensing: LicensingPluginSetup;
+}
+
+export interface RouteDependencies {
+ router: IRouter;
+ license: License;
+ lib: {
+ isEsError: typeof isEsError;
+ };
+}
diff --git a/x-pack/test/api_integration/apis/management/index.js b/x-pack/test/api_integration/apis/management/index.js
index 352cd56d0fc9f..cef2caa918620 100644
--- a/x-pack/test/api_integration/apis/management/index.js
+++ b/x-pack/test/api_integration/apis/management/index.js
@@ -12,5 +12,6 @@ export default function({ loadTestFile }) {
loadTestFile(require.resolve('./rollup'));
loadTestFile(require.resolve('./index_management'));
loadTestFile(require.resolve('./index_lifecycle_management'));
+ loadTestFile(require.resolve('./ingest_pipelines'));
});
}
diff --git a/x-pack/test/api_integration/apis/management/ingest_pipelines/index.ts b/x-pack/test/api_integration/apis/management/ingest_pipelines/index.ts
new file mode 100644
index 0000000000000..ca222ebc2c1e3
--- /dev/null
+++ b/x-pack/test/api_integration/apis/management/ingest_pipelines/index.ts
@@ -0,0 +1,12 @@
+/*
+ * 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 { FtrProviderContext } from '../../../ftr_provider_context';
+
+export default function({ loadTestFile }: FtrProviderContext) {
+ describe('Ingest Node Pipelines', () => {
+ loadTestFile(require.resolve('./ingest_pipelines'));
+ });
+}
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
new file mode 100644
index 0000000000000..88a78d048a3b6
--- /dev/null
+++ b/x-pack/test/api_integration/apis/management/ingest_pipelines/ingest_pipelines.ts
@@ -0,0 +1,330 @@
+/*
+ * 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 expect from '@kbn/expect';
+import { registerEsHelpers } from './lib';
+
+import { FtrProviderContext } from '../../../ftr_provider_context';
+
+const API_BASE_PATH = '/api/ingest_pipelines';
+
+export default function({ getService }: FtrProviderContext) {
+ const supertest = getService('supertest');
+
+ const { createPipeline, deletePipeline } = registerEsHelpers(getService);
+
+ describe('Pipelines', function() {
+ describe('Create', () => {
+ const PIPELINE_ID = 'test_create_pipeline';
+ after(() => deletePipeline(PIPELINE_ID));
+
+ it('should create a pipeline', async () => {
+ const { body } = await supertest
+ .post(API_BASE_PATH)
+ .set('kbn-xsrf', 'xxx')
+ .send({
+ name: PIPELINE_ID,
+ description: 'test pipeline description',
+ processors: [
+ {
+ script: {
+ source: 'ctx._type = null',
+ },
+ },
+ ],
+ on_failure: [
+ {
+ set: {
+ field: 'error.message',
+ value: '{{ failure_message }}',
+ },
+ },
+ ],
+ version: 1,
+ })
+ .expect(200);
+
+ expect(body).to.eql({
+ acknowledged: true,
+ });
+ });
+
+ it('should not allow creation of an existing pipeline', async () => {
+ const { body } = await supertest
+ .post(API_BASE_PATH)
+ .set('kbn-xsrf', 'xxx')
+ .send({
+ name: PIPELINE_ID,
+ description: 'test pipeline description',
+ processors: [
+ {
+ script: {
+ source: 'ctx._type = null',
+ },
+ },
+ ],
+ version: 1,
+ })
+ .expect(409);
+
+ expect(body).to.eql({
+ statusCode: 409,
+ error: 'Conflict',
+ message: `There is already a pipeline with name '${PIPELINE_ID}'.`,
+ });
+ });
+ });
+
+ describe('Update', () => {
+ const PIPELINE_ID = 'test_update_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));
+
+ it('should allow an existing pipeline to be updated', async () => {
+ const uri = `${API_BASE_PATH}/${PIPELINE_ID}`;
+
+ const { body } = await supertest
+ .put(uri)
+ .set('kbn-xsrf', 'xxx')
+ .send({
+ ...PIPELINE,
+ description: 'updated test pipeline description',
+ })
+ .expect(200);
+
+ expect(body).to.eql({
+ acknowledged: true,
+ });
+ });
+
+ it('should not allow a non-existing pipeline to be updated', async () => {
+ const uri = `${API_BASE_PATH}/pipeline_does_not_exist`;
+
+ const { body } = await supertest
+ .put(uri)
+ .set('kbn-xsrf', 'xxx')
+ .send({
+ ...PIPELINE,
+ description: 'updated test pipeline description',
+ })
+ .expect(404);
+
+ expect(body).to.eql({
+ statusCode: 404,
+ error: 'Not Found',
+ message: 'Not Found',
+ });
+ });
+ });
+
+ 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,
+ });
+ });
+ });
+ });
+
+ describe('Delete', () => {
+ const PIPELINE = {
+ description: 'test pipeline description',
+ processors: [
+ {
+ script: {
+ source: 'ctx._type = null',
+ },
+ },
+ ],
+ version: 1,
+ };
+
+ it('should delete a pipeline', async () => {
+ // Create pipeline to be deleted
+ const PIPELINE_ID = 'test_delete_pipeline';
+ createPipeline({ body: PIPELINE, id: PIPELINE_ID });
+
+ const uri = `${API_BASE_PATH}/${PIPELINE_ID}`;
+
+ const { body } = await supertest
+ .delete(uri)
+ .set('kbn-xsrf', 'xxx')
+ .expect(200);
+
+ expect(body).to.eql({
+ itemsDeleted: [PIPELINE_ID],
+ errors: [],
+ });
+ });
+
+ it('should delete multiple pipelines', async () => {
+ // Create pipelines to be deleted
+ const PIPELINE_ONE_ID = 'test_delete_pipeline_1';
+ const PIPELINE_TWO_ID = 'test_delete_pipeline_2';
+ createPipeline({ body: PIPELINE, id: PIPELINE_ONE_ID });
+ createPipeline({ body: PIPELINE, id: PIPELINE_TWO_ID });
+
+ const uri = `${API_BASE_PATH}/${PIPELINE_ONE_ID},${PIPELINE_TWO_ID}`;
+
+ const {
+ body: { itemsDeleted, errors },
+ } = await supertest
+ .delete(uri)
+ .set('kbn-xsrf', 'xxx')
+ .expect(200);
+
+ expect(errors).to.eql([]);
+
+ // The itemsDeleted array order isn't guaranteed, so we assert against each pipeline name instead
+ [PIPELINE_ONE_ID, PIPELINE_TWO_ID].forEach(pipelineName => {
+ expect(itemsDeleted.includes(pipelineName)).to.be(true);
+ });
+ });
+
+ it('should return an error for any pipelines not sucessfully deleted', async () => {
+ const PIPELINE_DOES_NOT_EXIST = 'pipeline_does_not_exist';
+
+ // Create pipeline to be deleted
+ const PIPELINE_ONE_ID = 'test_delete_pipeline_1';
+ createPipeline({ body: PIPELINE, id: PIPELINE_ONE_ID });
+
+ const uri = `${API_BASE_PATH}/${PIPELINE_ONE_ID},${PIPELINE_DOES_NOT_EXIST}`;
+
+ const { body } = await supertest
+ .delete(uri)
+ .set('kbn-xsrf', 'xxx')
+ .expect(200);
+
+ expect(body).to.eql({
+ itemsDeleted: [PIPELINE_ONE_ID],
+ errors: [
+ {
+ name: PIPELINE_DOES_NOT_EXIST,
+ error: {
+ msg: '[resource_not_found_exception] pipeline [pipeline_does_not_exist] is missing',
+ path: '/_ingest/pipeline/pipeline_does_not_exist',
+ query: {},
+ statusCode: 404,
+ response: JSON.stringify({
+ error: {
+ root_cause: [
+ {
+ type: 'resource_not_found_exception',
+ reason: 'pipeline [pipeline_does_not_exist] is missing',
+ },
+ ],
+ type: 'resource_not_found_exception',
+ reason: 'pipeline [pipeline_does_not_exist] is missing',
+ },
+ status: 404,
+ }),
+ },
+ },
+ ],
+ });
+ });
+ });
+
+ describe('Simulate', () => {
+ it('should successfully simulate a pipeline', async () => {
+ const { body } = await supertest
+ .post(`${API_BASE_PATH}/simulate`)
+ .set('kbn-xsrf', 'xxx')
+ .send({
+ pipeline: {
+ description: 'test simulate pipeline description',
+ processors: [
+ {
+ set: {
+ field: 'field2',
+ value: '_value',
+ },
+ },
+ ],
+ },
+ documents: [
+ {
+ _index: 'index',
+ _id: 'id',
+ _source: {
+ foo: 'bar',
+ },
+ },
+ {
+ _index: 'index',
+ _id: 'id',
+ _source: {
+ foo: 'rab',
+ },
+ },
+ ],
+ })
+ .expect(200);
+
+ // The simulate ES response is quite long and includes timestamps
+ // so for now, we just confirm the docs array is returned with the correct length
+ expect(body.docs?.length).to.eql(2);
+ });
+ });
+ });
+}
diff --git a/x-pack/test/api_integration/apis/management/ingest_pipelines/lib/elasticsearch.ts b/x-pack/test/api_integration/apis/management/ingest_pipelines/lib/elasticsearch.ts
new file mode 100644
index 0000000000000..2f42596a66b54
--- /dev/null
+++ b/x-pack/test/api_integration/apis/management/ingest_pipelines/lib/elasticsearch.ts
@@ -0,0 +1,39 @@
+/*
+ * 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 { FtrProviderContext } from '../../../../ftr_provider_context';
+
+interface Processor {
+ [key: string]: {
+ [key: string]: unknown;
+ };
+}
+
+interface Pipeline {
+ id: string;
+ body: {
+ description: string;
+ processors: Processor[];
+ version?: number;
+ };
+}
+
+/**
+ * Helpers to create and delete pipelines on the Elasticsearch instance
+ * during our tests.
+ * @param {ElasticsearchClient} es The Elasticsearch client instance
+ */
+export const registerEsHelpers = (getService: FtrProviderContext['getService']) => {
+ const es = getService('legacyEs');
+
+ const createPipeline = (pipeline: Pipeline) => es.ingest.putPipeline(pipeline);
+
+ const deletePipeline = (pipelineId: string) => es.ingest.deletePipeline({ id: pipelineId });
+
+ return {
+ createPipeline,
+ deletePipeline,
+ };
+};
diff --git a/x-pack/test/api_integration/apis/management/ingest_pipelines/lib/index.ts b/x-pack/test/api_integration/apis/management/ingest_pipelines/lib/index.ts
new file mode 100644
index 0000000000000..66ea0fe40c4ce
--- /dev/null
+++ b/x-pack/test/api_integration/apis/management/ingest_pipelines/lib/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 { registerEsHelpers } from './elasticsearch';
diff --git a/x-pack/test/functional/apps/ingest_pipelines/index.ts b/x-pack/test/functional/apps/ingest_pipelines/index.ts
new file mode 100644
index 0000000000000..87e7e70c0b5e0
--- /dev/null
+++ b/x-pack/test/functional/apps/ingest_pipelines/index.ts
@@ -0,0 +1,14 @@
+/*
+ * 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 { FtrProviderContext } from '../../ftr_provider_context';
+
+export default ({ loadTestFile }: FtrProviderContext) => {
+ describe('Ingest pipelines app', function() {
+ this.tags('ciGroup3');
+ loadTestFile(require.resolve('./ingest_pipelines'));
+ });
+};
diff --git a/x-pack/test/functional/apps/ingest_pipelines/ingest_pipelines.ts b/x-pack/test/functional/apps/ingest_pipelines/ingest_pipelines.ts
new file mode 100644
index 0000000000000..1b22f8f35d7ad
--- /dev/null
+++ b/x-pack/test/functional/apps/ingest_pipelines/ingest_pipelines.ts
@@ -0,0 +1,27 @@
+/*
+ * 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 expect from '@kbn/expect';
+import { FtrProviderContext } from '../../ftr_provider_context';
+
+export default ({ getPageObjects, getService }: FtrProviderContext) => {
+ const pageObjects = getPageObjects(['common', 'ingestPipelines']);
+ const log = getService('log');
+
+ describe('Ingest Pipelines', function() {
+ this.tags('smoke');
+ before(async () => {
+ await pageObjects.common.navigateToApp('ingestPipelines');
+ });
+
+ it('Loads the app', async () => {
+ await log.debug('Checking for section heading to say Ingest Node Pipelines.');
+
+ const headingText = await pageObjects.ingestPipelines.sectionHeadingText();
+ expect(headingText).to.be('Ingest Node Pipelines');
+ });
+ });
+};
diff --git a/x-pack/test/functional/config.js b/x-pack/test/functional/config.js
index 2c6238704bea0..f6b80b1b9fc67 100644
--- a/x-pack/test/functional/config.js
+++ b/x-pack/test/functional/config.js
@@ -53,6 +53,7 @@ export default async function({ readConfigFile }) {
resolve(__dirname, './apps/index_patterns'),
resolve(__dirname, './apps/index_management'),
resolve(__dirname, './apps/index_lifecycle_management'),
+ resolve(__dirname, './apps/ingest_pipelines'),
resolve(__dirname, './apps/snapshot_restore'),
resolve(__dirname, './apps/cross_cluster_replication'),
resolve(__dirname, './apps/remote_clusters'),
@@ -175,6 +176,10 @@ export default async function({ readConfigFile }) {
pathname: '/app/kibana',
hash: '/management/elasticsearch/index_lifecycle_management',
},
+ ingestPipelines: {
+ pathname: '/app/kibana',
+ hash: '/management/elasticsearch/ingest_pipelines',
+ },
snapshotRestore: {
pathname: '/app/kibana',
hash: '/management/elasticsearch/snapshot_restore',
diff --git a/x-pack/test/functional/page_objects/index.ts b/x-pack/test/functional/page_objects/index.ts
index 4b8c2944ef190..833cc452a5d31 100644
--- a/x-pack/test/functional/page_objects/index.ts
+++ b/x-pack/test/functional/page_objects/index.ts
@@ -45,6 +45,7 @@ import { LensPageProvider } from './lens_page';
import { InfraMetricExplorerProvider } from './infra_metric_explorer';
import { RoleMappingsPageProvider } from './role_mappings_page';
import { SpaceSelectorPageProvider } from './space_selector_page';
+import { IngestPipelinesPageProvider } from './ingest_pipelines_page';
// just like services, PageObjects are defined as a map of
// names to Providers. Merge in Kibana's or pick specific ones
@@ -78,4 +79,5 @@ export const pageObjects = {
copySavedObjectsToSpace: CopySavedObjectsToSpacePageProvider,
lens: LensPageProvider,
roleMappings: RoleMappingsPageProvider,
+ ingestPipelines: IngestPipelinesPageProvider,
};
diff --git a/x-pack/test/functional/page_objects/ingest_pipelines_page.ts b/x-pack/test/functional/page_objects/ingest_pipelines_page.ts
new file mode 100644
index 0000000000000..abc85277a3617
--- /dev/null
+++ b/x-pack/test/functional/page_objects/ingest_pipelines_page.ts
@@ -0,0 +1,17 @@
+/*
+ * 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 { FtrProviderContext } from '../ftr_provider_context';
+
+export function IngestPipelinesPageProvider({ getService }: FtrProviderContext) {
+ const testSubjects = getService('testSubjects');
+
+ return {
+ async sectionHeadingText() {
+ return await testSubjects.getVisibleText('appTitle');
+ },
+ };
+}
From 59315bc84d3fbf8bde3b7bebdf0308b1749ed978 Mon Sep 17 00:00:00 2001
From: igoristic
-
- For more license options please visit
-
+
+ For more license options please visit
+
-
- {title}
+
+
+
|\r\n|\r/g, "\n").split("\n");
+
+ for (var i = 0; i < lines.length; ++i) {
+
+ var lineText = lines[i],
+ measured = context.measureText(lineText);
+
+ info.width = Math.max(measured.width, info.width);
+ info.height += font.lineHeight;
+
+ info.lines.push({
+ text: lineText,
+ width: measured.width,
+ height: font.lineHeight
+ });
+ }
+
+ context.restore();
+ }
+
+ return info;
+ };
+
+ // Adds a text string to the canvas text overlay.
+
+ Canvas.prototype.addText = function(layer, x, y, text, font, angle, width, halign, valign) {
+
+ if (!plot.getOptions().canvas) {
+ return addText.call(this, layer, x, y, text, font, angle, width, halign, valign);
+ }
+
+ var info = this.getTextInfo(layer, text, font, angle, width),
+ positions = info.positions,
+ lines = info.lines;
+
+ // Text is drawn with baseline 'middle', which we need to account
+ // for by adding half a line's height to the y position.
+
+ y += info.height / lines.length / 2;
+
+ // Tweak the initial y-position to match vertical alignment
+
+ if (valign == "middle") {
+ y = Math.round(y - info.height / 2);
+ } else if (valign == "bottom") {
+ y = Math.round(y - info.height);
+ } else {
+ y = Math.round(y);
+ }
+
+ // FIXME: LEGACY BROWSER FIX
+ // AFFECTS: Opera < 12.00
+
+ // Offset the y coordinate, since Opera is off pretty
+ // consistently compared to the other browsers.
+
+ if (!!(window.opera && window.opera.version().split(".")[0] < 12)) {
+ y -= 2;
+ }
+
+ // Determine whether this text already exists at this position.
+ // If so, mark it for inclusion in the next render pass.
+
+ for (var i = 0, position; position = positions[i]; i++) {
+ if (position.x == x && position.y == y) {
+ position.active = true;
+ return;
+ }
+ }
+
+ // If the text doesn't exist at this position, create a new entry
+
+ position = {
+ active: true,
+ lines: [],
+ x: x,
+ y: y
+ };
+
+ positions.push(position);
+
+ // Fill in the x & y positions of each line, adjusting them
+ // individually for horizontal alignment.
+
+ for (var i = 0, line; line = lines[i]; i++) {
+ if (halign == "center") {
+ position.lines.push([Math.round(x - line.width / 2), y]);
+ } else if (halign == "right") {
+ position.lines.push([Math.round(x - line.width), y]);
+ } else {
+ position.lines.push([Math.round(x), y]);
+ }
+ y += line.height;
+ }
+ };
+ }
+
+ $.plot.plugins.push({
+ init: init,
+ options: options,
+ name: "canvas",
+ version: "1.0"
+ });
+
+})(jQuery);
diff --git a/x-pack/plugins/monitoring/public/lib/jquery_flot/flot-charts/jquery.flot.categories.js b/x-pack/plugins/monitoring/public/lib/jquery_flot/flot-charts/jquery.flot.categories.js
new file mode 100644
index 0000000000000..2f9b257971499
--- /dev/null
+++ b/x-pack/plugins/monitoring/public/lib/jquery_flot/flot-charts/jquery.flot.categories.js
@@ -0,0 +1,190 @@
+/* Flot plugin for plotting textual data or categories.
+
+Copyright (c) 2007-2014 IOLA and Ole Laursen.
+Licensed under the MIT license.
+
+Consider a dataset like [["February", 34], ["March", 20], ...]. This plugin
+allows you to plot such a dataset directly.
+
+To enable it, you must specify mode: "categories" on the axis with the textual
+labels, e.g.
+
+ $.plot("#placeholder", data, { xaxis: { mode: "categories" } });
+
+By default, the labels are ordered as they are met in the data series. If you
+need a different ordering, you can specify "categories" on the axis options
+and list the categories there:
+
+ xaxis: {
+ mode: "categories",
+ categories: ["February", "March", "April"]
+ }
+
+If you need to customize the distances between the categories, you can specify
+"categories" as an object mapping labels to values
+
+ xaxis: {
+ mode: "categories",
+ categories: { "February": 1, "March": 3, "April": 4 }
+ }
+
+If you don't specify all categories, the remaining categories will be numbered
+from the max value plus 1 (with a spacing of 1 between each).
+
+Internally, the plugin works by transforming the input data through an auto-
+generated mapping where the first category becomes 0, the second 1, etc.
+Hence, a point like ["February", 34] becomes [0, 34] internally in Flot (this
+is visible in hover and click events that return numbers rather than the
+category labels). The plugin also overrides the tick generator to spit out the
+categories as ticks instead of the values.
+
+If you need to map a value back to its label, the mapping is always accessible
+as "categories" on the axis object, e.g. plot.getAxes().xaxis.categories.
+
+*/
+
+(function ($) {
+ var options = {
+ xaxis: {
+ categories: null
+ },
+ yaxis: {
+ categories: null
+ }
+ };
+
+ function processRawData(plot, series, data, datapoints) {
+ // if categories are enabled, we need to disable
+ // auto-transformation to numbers so the strings are intact
+ // for later processing
+
+ var xCategories = series.xaxis.options.mode == "categories",
+ yCategories = series.yaxis.options.mode == "categories";
+
+ if (!(xCategories || yCategories))
+ return;
+
+ var format = datapoints.format;
+
+ if (!format) {
+ // FIXME: auto-detection should really not be defined here
+ var s = series;
+ format = [];
+ format.push({ x: true, number: true, required: true });
+ format.push({ y: true, number: true, required: true });
+
+ if (s.bars.show || (s.lines.show && s.lines.fill)) {
+ var autoscale = !!((s.bars.show && s.bars.zero) || (s.lines.show && s.lines.zero));
+ format.push({ y: true, number: true, required: false, defaultValue: 0, autoscale: autoscale });
+ if (s.bars.horizontal) {
+ delete format[format.length - 1].y;
+ format[format.length - 1].x = true;
+ }
+ }
+
+ datapoints.format = format;
+ }
+
+ for (var m = 0; m < format.length; ++m) {
+ if (format[m].x && xCategories)
+ format[m].number = false;
+
+ if (format[m].y && yCategories)
+ format[m].number = false;
+ }
+ }
+
+ function getNextIndex(categories) {
+ var index = -1;
+
+ for (var v in categories)
+ if (categories[v] > index)
+ index = categories[v];
+
+ return index + 1;
+ }
+
+ function categoriesTickGenerator(axis) {
+ var res = [];
+ for (var label in axis.categories) {
+ var v = axis.categories[label];
+ if (v >= axis.min && v <= axis.max)
+ res.push([v, label]);
+ }
+
+ res.sort(function (a, b) { return a[0] - b[0]; });
+
+ return res;
+ }
+
+ function setupCategoriesForAxis(series, axis, datapoints) {
+ if (series[axis].options.mode != "categories")
+ return;
+
+ if (!series[axis].categories) {
+ // parse options
+ var c = {}, o = series[axis].options.categories || {};
+ if ($.isArray(o)) {
+ for (var i = 0; i < o.length; ++i)
+ c[o[i]] = i;
+ }
+ else {
+ for (var v in o)
+ c[v] = o[v];
+ }
+
+ series[axis].categories = c;
+ }
+
+ // fix ticks
+ if (!series[axis].options.ticks)
+ series[axis].options.ticks = categoriesTickGenerator;
+
+ transformPointsOnAxis(datapoints, axis, series[axis].categories);
+ }
+
+ function transformPointsOnAxis(datapoints, axis, categories) {
+ // go through the points, transforming them
+ var points = datapoints.points,
+ ps = datapoints.pointsize,
+ format = datapoints.format,
+ formatColumn = axis.charAt(0),
+ index = getNextIndex(categories);
+
+ for (var i = 0; i < points.length; i += ps) {
+ if (points[i] == null)
+ continue;
+
+ for (var m = 0; m < ps; ++m) {
+ var val = points[i + m];
+
+ if (val == null || !format[m][formatColumn])
+ continue;
+
+ if (!(val in categories)) {
+ categories[val] = index;
+ ++index;
+ }
+
+ points[i + m] = categories[val];
+ }
+ }
+ }
+
+ function processDatapoints(plot, series, datapoints) {
+ setupCategoriesForAxis(series, "xaxis", datapoints);
+ setupCategoriesForAxis(series, "yaxis", datapoints);
+ }
+
+ function init(plot) {
+ plot.hooks.processRawData.push(processRawData);
+ plot.hooks.processDatapoints.push(processDatapoints);
+ }
+
+ $.plot.plugins.push({
+ init: init,
+ options: options,
+ name: 'categories',
+ version: '1.0'
+ });
+})(jQuery);
diff --git a/x-pack/plugins/monitoring/public/lib/jquery_flot/flot-charts/jquery.flot.crosshair.js b/x-pack/plugins/monitoring/public/lib/jquery_flot/flot-charts/jquery.flot.crosshair.js
new file mode 100644
index 0000000000000..5111695e3d12c
--- /dev/null
+++ b/x-pack/plugins/monitoring/public/lib/jquery_flot/flot-charts/jquery.flot.crosshair.js
@@ -0,0 +1,176 @@
+/* Flot plugin for showing crosshairs when the mouse hovers over the plot.
+
+Copyright (c) 2007-2014 IOLA and Ole Laursen.
+Licensed under the MIT license.
+
+The plugin supports these options:
+
+ crosshair: {
+ mode: null or "x" or "y" or "xy"
+ color: color
+ lineWidth: number
+ }
+
+Set the mode to one of "x", "y" or "xy". The "x" mode enables a vertical
+crosshair that lets you trace the values on the x axis, "y" enables a
+horizontal crosshair and "xy" enables them both. "color" is the color of the
+crosshair (default is "rgba(170, 0, 0, 0.80)"), "lineWidth" is the width of
+the drawn lines (default is 1).
+
+The plugin also adds four public methods:
+
+ - setCrosshair( pos )
+
+ Set the position of the crosshair. Note that this is cleared if the user
+ moves the mouse. "pos" is in coordinates of the plot and should be on the
+ form { x: xpos, y: ypos } (you can use x2/x3/... if you're using multiple
+ axes), which is coincidentally the same format as what you get from a
+ "plothover" event. If "pos" is null, the crosshair is cleared.
+
+ - clearCrosshair()
+
+ Clear the crosshair.
+
+ - lockCrosshair(pos)
+
+ Cause the crosshair to lock to the current location, no longer updating if
+ the user moves the mouse. Optionally supply a position (passed on to
+ setCrosshair()) to move it to.
+
+ Example usage:
+
+ var myFlot = $.plot( $("#graph"), ..., { crosshair: { mode: "x" } } };
+ $("#graph").bind( "plothover", function ( evt, position, item ) {
+ if ( item ) {
+ // Lock the crosshair to the data point being hovered
+ myFlot.lockCrosshair({
+ x: item.datapoint[ 0 ],
+ y: item.datapoint[ 1 ]
+ });
+ } else {
+ // Return normal crosshair operation
+ myFlot.unlockCrosshair();
+ }
+ });
+
+ - unlockCrosshair()
+
+ Free the crosshair to move again after locking it.
+*/
+
+(function ($) {
+ var options = {
+ crosshair: {
+ mode: null, // one of null, "x", "y" or "xy",
+ color: "rgba(170, 0, 0, 0.80)",
+ lineWidth: 1
+ }
+ };
+
+ function init(plot) {
+ // position of crosshair in pixels
+ var crosshair = { x: -1, y: -1, locked: false };
+
+ plot.setCrosshair = function setCrosshair(pos) {
+ if (!pos)
+ crosshair.x = -1;
+ else {
+ var o = plot.p2c(pos);
+ crosshair.x = Math.max(0, Math.min(o.left, plot.width()));
+ crosshair.y = Math.max(0, Math.min(o.top, plot.height()));
+ }
+
+ plot.triggerRedrawOverlay();
+ };
+
+ plot.clearCrosshair = plot.setCrosshair; // passes null for pos
+
+ plot.lockCrosshair = function lockCrosshair(pos) {
+ if (pos)
+ plot.setCrosshair(pos);
+ crosshair.locked = true;
+ };
+
+ plot.unlockCrosshair = function unlockCrosshair() {
+ crosshair.locked = false;
+ };
+
+ function onMouseOut(e) {
+ if (crosshair.locked)
+ return;
+
+ if (crosshair.x != -1) {
+ crosshair.x = -1;
+ plot.triggerRedrawOverlay();
+ }
+ }
+
+ function onMouseMove(e) {
+ if (crosshair.locked)
+ return;
+
+ if (plot.getSelection && plot.getSelection()) {
+ crosshair.x = -1; // hide the crosshair while selecting
+ return;
+ }
+
+ var offset = plot.offset();
+ crosshair.x = Math.max(0, Math.min(e.pageX - offset.left, plot.width()));
+ crosshair.y = Math.max(0, Math.min(e.pageY - offset.top, plot.height()));
+ plot.triggerRedrawOverlay();
+ }
+
+ plot.hooks.bindEvents.push(function (plot, eventHolder) {
+ if (!plot.getOptions().crosshair.mode)
+ return;
+
+ eventHolder.mouseout(onMouseOut);
+ eventHolder.mousemove(onMouseMove);
+ });
+
+ plot.hooks.drawOverlay.push(function (plot, ctx) {
+ var c = plot.getOptions().crosshair;
+ if (!c.mode)
+ return;
+
+ var plotOffset = plot.getPlotOffset();
+
+ ctx.save();
+ ctx.translate(plotOffset.left, plotOffset.top);
+
+ if (crosshair.x != -1) {
+ var adj = plot.getOptions().crosshair.lineWidth % 2 ? 0.5 : 0;
+
+ ctx.strokeStyle = c.color;
+ ctx.lineWidth = c.lineWidth;
+ ctx.lineJoin = "round";
+
+ ctx.beginPath();
+ if (c.mode.indexOf("x") != -1) {
+ var drawX = Math.floor(crosshair.x) + adj;
+ ctx.moveTo(drawX, 0);
+ ctx.lineTo(drawX, plot.height());
+ }
+ if (c.mode.indexOf("y") != -1) {
+ var drawY = Math.floor(crosshair.y) + adj;
+ ctx.moveTo(0, drawY);
+ ctx.lineTo(plot.width(), drawY);
+ }
+ ctx.stroke();
+ }
+ ctx.restore();
+ });
+
+ plot.hooks.shutdown.push(function (plot, eventHolder) {
+ eventHolder.unbind("mouseout", onMouseOut);
+ eventHolder.unbind("mousemove", onMouseMove);
+ });
+ }
+
+ $.plot.plugins.push({
+ init: init,
+ options: options,
+ name: 'crosshair',
+ version: '1.0'
+ });
+})(jQuery);
diff --git a/x-pack/plugins/monitoring/public/lib/jquery_flot/flot-charts/jquery.flot.errorbars.js b/x-pack/plugins/monitoring/public/lib/jquery_flot/flot-charts/jquery.flot.errorbars.js
new file mode 100644
index 0000000000000..655036e0db846
--- /dev/null
+++ b/x-pack/plugins/monitoring/public/lib/jquery_flot/flot-charts/jquery.flot.errorbars.js
@@ -0,0 +1,353 @@
+/* Flot plugin for plotting error bars.
+
+Copyright (c) 2007-2014 IOLA and Ole Laursen.
+Licensed under the MIT license.
+
+Error bars are used to show standard deviation and other statistical
+properties in a plot.
+
+* Created by Rui Pereira - rui (dot) pereira (at) gmail (dot) com
+
+This plugin allows you to plot error-bars over points. Set "errorbars" inside
+the points series to the axis name over which there will be error values in
+your data array (*even* if you do not intend to plot them later, by setting
+"show: null" on xerr/yerr).
+
+The plugin supports these options:
+
+ series: {
+ points: {
+ errorbars: "x" or "y" or "xy",
+ xerr: {
+ show: null/false or true,
+ asymmetric: null/false or true,
+ upperCap: null or "-" or function,
+ lowerCap: null or "-" or function,
+ color: null or color,
+ radius: null or number
+ },
+ yerr: { same options as xerr }
+ }
+ }
+
+Each data point array is expected to be of the type:
+
+ "x" [ x, y, xerr ]
+ "y" [ x, y, yerr ]
+ "xy" [ x, y, xerr, yerr ]
+
+Where xerr becomes xerr_lower,xerr_upper for the asymmetric error case, and
+equivalently for yerr. E.g., a datapoint for the "xy" case with symmetric
+error-bars on X and asymmetric on Y would be:
+
+ [ x, y, xerr, yerr_lower, yerr_upper ]
+
+By default no end caps are drawn. Setting upperCap and/or lowerCap to "-" will
+draw a small cap perpendicular to the error bar. They can also be set to a
+user-defined drawing function, with (ctx, x, y, radius) as parameters, as e.g.:
+
+ function drawSemiCircle( ctx, x, y, radius ) {
+ ctx.beginPath();
+ ctx.arc( x, y, radius, 0, Math.PI, false );
+ ctx.moveTo( x - radius, y );
+ ctx.lineTo( x + radius, y );
+ ctx.stroke();
+ }
+
+Color and radius both default to the same ones of the points series if not
+set. The independent radius parameter on xerr/yerr is useful for the case when
+we may want to add error-bars to a line, without showing the interconnecting
+points (with radius: 0), and still showing end caps on the error-bars.
+shadowSize and lineWidth are derived as well from the points series.
+
+*/
+
+(function ($) {
+ var options = {
+ series: {
+ points: {
+ errorbars: null, //should be 'x', 'y' or 'xy'
+ xerr: { err: 'x', show: null, asymmetric: null, upperCap: null, lowerCap: null, color: null, radius: null},
+ yerr: { err: 'y', show: null, asymmetric: null, upperCap: null, lowerCap: null, color: null, radius: null}
+ }
+ }
+ };
+
+ function processRawData(plot, series, data, datapoints){
+ if (!series.points.errorbars)
+ return;
+
+ // x,y values
+ var format = [
+ { x: true, number: true, required: true },
+ { y: true, number: true, required: true }
+ ];
+
+ var errors = series.points.errorbars;
+ // error bars - first X then Y
+ if (errors == 'x' || errors == 'xy') {
+ // lower / upper error
+ if (series.points.xerr.asymmetric) {
+ format.push({ x: true, number: true, required: true });
+ format.push({ x: true, number: true, required: true });
+ } else
+ format.push({ x: true, number: true, required: true });
+ }
+ if (errors == 'y' || errors == 'xy') {
+ // lower / upper error
+ if (series.points.yerr.asymmetric) {
+ format.push({ y: true, number: true, required: true });
+ format.push({ y: true, number: true, required: true });
+ } else
+ format.push({ y: true, number: true, required: true });
+ }
+ datapoints.format = format;
+ }
+
+ function parseErrors(series, i){
+
+ var points = series.datapoints.points;
+
+ // read errors from points array
+ var exl = null,
+ exu = null,
+ eyl = null,
+ eyu = null;
+ var xerr = series.points.xerr,
+ yerr = series.points.yerr;
+
+ var eb = series.points.errorbars;
+ // error bars - first X
+ if (eb == 'x' || eb == 'xy') {
+ if (xerr.asymmetric) {
+ exl = points[i + 2];
+ exu = points[i + 3];
+ if (eb == 'xy')
+ if (yerr.asymmetric){
+ eyl = points[i + 4];
+ eyu = points[i + 5];
+ } else eyl = points[i + 4];
+ } else {
+ exl = points[i + 2];
+ if (eb == 'xy')
+ if (yerr.asymmetric) {
+ eyl = points[i + 3];
+ eyu = points[i + 4];
+ } else eyl = points[i + 3];
+ }
+ // only Y
+ } else if (eb == 'y')
+ if (yerr.asymmetric) {
+ eyl = points[i + 2];
+ eyu = points[i + 3];
+ } else eyl = points[i + 2];
+
+ // symmetric errors?
+ if (exu == null) exu = exl;
+ if (eyu == null) eyu = eyl;
+
+ var errRanges = [exl, exu, eyl, eyu];
+ // nullify if not showing
+ if (!xerr.show){
+ errRanges[0] = null;
+ errRanges[1] = null;
+ }
+ if (!yerr.show){
+ errRanges[2] = null;
+ errRanges[3] = null;
+ }
+ return errRanges;
+ }
+
+ function drawSeriesErrors(plot, ctx, s){
+
+ var points = s.datapoints.points,
+ ps = s.datapoints.pointsize,
+ ax = [s.xaxis, s.yaxis],
+ radius = s.points.radius,
+ err = [s.points.xerr, s.points.yerr];
+
+ //sanity check, in case some inverted axis hack is applied to flot
+ var invertX = false;
+ if (ax[0].p2c(ax[0].max) < ax[0].p2c(ax[0].min)) {
+ invertX = true;
+ var tmp = err[0].lowerCap;
+ err[0].lowerCap = err[0].upperCap;
+ err[0].upperCap = tmp;
+ }
+
+ var invertY = false;
+ if (ax[1].p2c(ax[1].min) < ax[1].p2c(ax[1].max)) {
+ invertY = true;
+ var tmp = err[1].lowerCap;
+ err[1].lowerCap = err[1].upperCap;
+ err[1].upperCap = tmp;
+ }
+
+ for (var i = 0; i < s.datapoints.points.length; i += ps) {
+
+ //parse
+ var errRanges = parseErrors(s, i);
+
+ //cycle xerr & yerr
+ for (var e = 0; e < err.length; e++){
+
+ var minmax = [ax[e].min, ax[e].max];
+
+ //draw this error?
+ if (errRanges[e * err.length]){
+
+ //data coordinates
+ var x = points[i],
+ y = points[i + 1];
+
+ //errorbar ranges
+ var upper = [x, y][e] + errRanges[e * err.length + 1],
+ lower = [x, y][e] - errRanges[e * err.length];
+
+ //points outside of the canvas
+ if (err[e].err == 'x')
+ if (y > ax[1].max || y < ax[1].min || upper < ax[0].min || lower > ax[0].max)
+ continue;
+ if (err[e].err == 'y')
+ if (x > ax[0].max || x < ax[0].min || upper < ax[1].min || lower > ax[1].max)
+ continue;
+
+ // prevent errorbars getting out of the canvas
+ var drawUpper = true,
+ drawLower = true;
+
+ if (upper > minmax[1]) {
+ drawUpper = false;
+ upper = minmax[1];
+ }
+ if (lower < minmax[0]) {
+ drawLower = false;
+ lower = minmax[0];
+ }
+
+ //sanity check, in case some inverted axis hack is applied to flot
+ if ((err[e].err == 'x' && invertX) || (err[e].err == 'y' && invertY)) {
+ //swap coordinates
+ var tmp = lower;
+ lower = upper;
+ upper = tmp;
+ tmp = drawLower;
+ drawLower = drawUpper;
+ drawUpper = tmp;
+ tmp = minmax[0];
+ minmax[0] = minmax[1];
+ minmax[1] = tmp;
+ }
+
+ // convert to pixels
+ x = ax[0].p2c(x),
+ y = ax[1].p2c(y),
+ upper = ax[e].p2c(upper);
+ lower = ax[e].p2c(lower);
+ minmax[0] = ax[e].p2c(minmax[0]);
+ minmax[1] = ax[e].p2c(minmax[1]);
+
+ //same style as points by default
+ var lw = err[e].lineWidth ? err[e].lineWidth : s.points.lineWidth,
+ sw = s.points.shadowSize != null ? s.points.shadowSize : s.shadowSize;
+
+ //shadow as for points
+ if (lw > 0 && sw > 0) {
+ var w = sw / 2;
+ ctx.lineWidth = w;
+ ctx.strokeStyle = "rgba(0,0,0,0.1)";
+ drawError(ctx, err[e], x, y, upper, lower, drawUpper, drawLower, radius, w + w/2, minmax);
+
+ ctx.strokeStyle = "rgba(0,0,0,0.2)";
+ drawError(ctx, err[e], x, y, upper, lower, drawUpper, drawLower, radius, w/2, minmax);
+ }
+
+ ctx.strokeStyle = err[e].color? err[e].color: s.color;
+ ctx.lineWidth = lw;
+ //draw it
+ drawError(ctx, err[e], x, y, upper, lower, drawUpper, drawLower, radius, 0, minmax);
+ }
+ }
+ }
+ }
+
+ function drawError(ctx,err,x,y,upper,lower,drawUpper,drawLower,radius,offset,minmax){
+
+ //shadow offset
+ y += offset;
+ upper += offset;
+ lower += offset;
+
+ // error bar - avoid plotting over circles
+ if (err.err == 'x'){
+ if (upper > x + radius) drawPath(ctx, [[upper,y],[Math.max(x + radius,minmax[0]),y]]);
+ else drawUpper = false;
+ if (lower < x - radius) drawPath(ctx, [[Math.min(x - radius,minmax[1]),y],[lower,y]] );
+ else drawLower = false;
+ }
+ else {
+ if (upper < y - radius) drawPath(ctx, [[x,upper],[x,Math.min(y - radius,minmax[0])]] );
+ else drawUpper = false;
+ if (lower > y + radius) drawPath(ctx, [[x,Math.max(y + radius,minmax[1])],[x,lower]] );
+ else drawLower = false;
+ }
+
+ //internal radius value in errorbar, allows to plot radius 0 points and still keep proper sized caps
+ //this is a way to get errorbars on lines without visible connecting dots
+ radius = err.radius != null? err.radius: radius;
+
+ // upper cap
+ if (drawUpper) {
+ if (err.upperCap == '-'){
+ if (err.err=='x') drawPath(ctx, [[upper,y - radius],[upper,y + radius]] );
+ else drawPath(ctx, [[x - radius,upper],[x + radius,upper]] );
+ } else if ($.isFunction(err.upperCap)){
+ if (err.err=='x') err.upperCap(ctx, upper, y, radius);
+ else err.upperCap(ctx, x, upper, radius);
+ }
+ }
+ // lower cap
+ if (drawLower) {
+ if (err.lowerCap == '-'){
+ if (err.err=='x') drawPath(ctx, [[lower,y - radius],[lower,y + radius]] );
+ else drawPath(ctx, [[x - radius,lower],[x + radius,lower]] );
+ } else if ($.isFunction(err.lowerCap)){
+ if (err.err=='x') err.lowerCap(ctx, lower, y, radius);
+ else err.lowerCap(ctx, x, lower, radius);
+ }
+ }
+ }
+
+ function drawPath(ctx, pts){
+ ctx.beginPath();
+ ctx.moveTo(pts[0][0], pts[0][1]);
+ for (var p=1; p < pts.length; p++)
+ ctx.lineTo(pts[p][0], pts[p][1]);
+ ctx.stroke();
+ }
+
+ function draw(plot, ctx){
+ var plotOffset = plot.getPlotOffset();
+
+ ctx.save();
+ ctx.translate(plotOffset.left, plotOffset.top);
+ $.each(plot.getData(), function (i, s) {
+ if (s.points.errorbars && (s.points.xerr.show || s.points.yerr.show))
+ drawSeriesErrors(plot, ctx, s);
+ });
+ ctx.restore();
+ }
+
+ function init(plot) {
+ plot.hooks.processRawData.push(processRawData);
+ plot.hooks.draw.push(draw);
+ }
+
+ $.plot.plugins.push({
+ init: init,
+ options: options,
+ name: 'errorbars',
+ version: '1.0'
+ });
+})(jQuery);
diff --git a/x-pack/plugins/monitoring/public/lib/jquery_flot/flot-charts/jquery.flot.fillbetween.js b/x-pack/plugins/monitoring/public/lib/jquery_flot/flot-charts/jquery.flot.fillbetween.js
new file mode 100644
index 0000000000000..18b15d26db8c9
--- /dev/null
+++ b/x-pack/plugins/monitoring/public/lib/jquery_flot/flot-charts/jquery.flot.fillbetween.js
@@ -0,0 +1,226 @@
+/* Flot plugin for computing bottoms for filled line and bar charts.
+
+Copyright (c) 2007-2014 IOLA and Ole Laursen.
+Licensed under the MIT license.
+
+The case: you've got two series that you want to fill the area between. In Flot
+terms, you need to use one as the fill bottom of the other. You can specify the
+bottom of each data point as the third coordinate manually, or you can use this
+plugin to compute it for you.
+
+In order to name the other series, you need to give it an id, like this:
+
+ var dataset = [
+ { data: [ ... ], id: "foo" } , // use default bottom
+ { data: [ ... ], fillBetween: "foo" }, // use first dataset as bottom
+ ];
+
+ $.plot($("#placeholder"), dataset, { lines: { show: true, fill: true }});
+
+As a convenience, if the id given is a number that doesn't appear as an id in
+the series, it is interpreted as the index in the array instead (so fillBetween:
+0 can also mean the first series).
+
+Internally, the plugin modifies the datapoints in each series. For line series,
+extra data points might be inserted through interpolation. Note that at points
+where the bottom line is not defined (due to a null point or start/end of line),
+the current line will show a gap too. The algorithm comes from the
+jquery.flot.stack.js plugin, possibly some code could be shared.
+
+*/
+
+(function ( $ ) {
+
+ var options = {
+ series: {
+ fillBetween: null // or number
+ }
+ };
+
+ function init( plot ) {
+
+ function findBottomSeries( s, allseries ) {
+
+ var i;
+
+ for ( i = 0; i < allseries.length; ++i ) {
+ if ( allseries[ i ].id === s.fillBetween ) {
+ return allseries[ i ];
+ }
+ }
+
+ if ( typeof s.fillBetween === "number" ) {
+ if ( s.fillBetween < 0 || s.fillBetween >= allseries.length ) {
+ return null;
+ }
+ return allseries[ s.fillBetween ];
+ }
+
+ return null;
+ }
+
+ function computeFillBottoms( plot, s, datapoints ) {
+
+ if ( s.fillBetween == null ) {
+ return;
+ }
+
+ var other = findBottomSeries( s, plot.getData() );
+
+ if ( !other ) {
+ return;
+ }
+
+ var ps = datapoints.pointsize,
+ points = datapoints.points,
+ otherps = other.datapoints.pointsize,
+ otherpoints = other.datapoints.points,
+ newpoints = [],
+ px, py, intery, qx, qy, bottom,
+ withlines = s.lines.show,
+ withbottom = ps > 2 && datapoints.format[2].y,
+ withsteps = withlines && s.lines.steps,
+ fromgap = true,
+ i = 0,
+ j = 0,
+ l, m;
+
+ while ( true ) {
+
+ if ( i >= points.length ) {
+ break;
+ }
+
+ l = newpoints.length;
+
+ if ( points[ i ] == null ) {
+
+ // copy gaps
+
+ for ( m = 0; m < ps; ++m ) {
+ newpoints.push( points[ i + m ] );
+ }
+
+ i += ps;
+
+ } else if ( j >= otherpoints.length ) {
+
+ // for lines, we can't use the rest of the points
+
+ if ( !withlines ) {
+ for ( m = 0; m < ps; ++m ) {
+ newpoints.push( points[ i + m ] );
+ }
+ }
+
+ i += ps;
+
+ } else if ( otherpoints[ j ] == null ) {
+
+ // oops, got a gap
+
+ for ( m = 0; m < ps; ++m ) {
+ newpoints.push( null );
+ }
+
+ fromgap = true;
+ j += otherps;
+
+ } else {
+
+ // cases where we actually got two points
+
+ px = points[ i ];
+ py = points[ i + 1 ];
+ qx = otherpoints[ j ];
+ qy = otherpoints[ j + 1 ];
+ bottom = 0;
+
+ if ( px === qx ) {
+
+ for ( m = 0; m < ps; ++m ) {
+ newpoints.push( points[ i + m ] );
+ }
+
+ //newpoints[ l + 1 ] += qy;
+ bottom = qy;
+
+ i += ps;
+ j += otherps;
+
+ } else if ( px > qx ) {
+
+ // we got past point below, might need to
+ // insert interpolated extra point
+
+ if ( withlines && i > 0 && points[ i - ps ] != null ) {
+ intery = py + ( points[ i - ps + 1 ] - py ) * ( qx - px ) / ( points[ i - ps ] - px );
+ newpoints.push( qx );
+ newpoints.push( intery );
+ for ( m = 2; m < ps; ++m ) {
+ newpoints.push( points[ i + m ] );
+ }
+ bottom = qy;
+ }
+
+ j += otherps;
+
+ } else { // px < qx
+
+ // if we come from a gap, we just skip this point
+
+ if ( fromgap && withlines ) {
+ i += ps;
+ continue;
+ }
+
+ for ( m = 0; m < ps; ++m ) {
+ newpoints.push( points[ i + m ] );
+ }
+
+ // we might be able to interpolate a point below,
+ // this can give us a better y
+
+ if ( withlines && j > 0 && otherpoints[ j - otherps ] != null ) {
+ bottom = qy + ( otherpoints[ j - otherps + 1 ] - qy ) * ( px - qx ) / ( otherpoints[ j - otherps ] - qx );
+ }
+
+ //newpoints[l + 1] += bottom;
+
+ i += ps;
+ }
+
+ fromgap = false;
+
+ if ( l !== newpoints.length && withbottom ) {
+ newpoints[ l + 2 ] = bottom;
+ }
+ }
+
+ // maintain the line steps invariant
+
+ if ( withsteps && l !== newpoints.length && l > 0 &&
+ newpoints[ l ] !== null &&
+ newpoints[ l ] !== newpoints[ l - ps ] &&
+ newpoints[ l + 1 ] !== newpoints[ l - ps + 1 ] ) {
+ for (m = 0; m < ps; ++m) {
+ newpoints[ l + ps + m ] = newpoints[ l + m ];
+ }
+ newpoints[ l + 1 ] = newpoints[ l - ps + 1 ];
+ }
+ }
+
+ datapoints.points = newpoints;
+ }
+
+ plot.hooks.processDatapoints.push( computeFillBottoms );
+ }
+
+ $.plot.plugins.push({
+ init: init,
+ options: options,
+ name: "fillbetween",
+ version: "1.0"
+ });
+
+})(jQuery);
diff --git a/x-pack/plugins/monitoring/public/lib/jquery_flot/flot-charts/jquery.flot.image.js b/x-pack/plugins/monitoring/public/lib/jquery_flot/flot-charts/jquery.flot.image.js
new file mode 100644
index 0000000000000..178f0e69069ef
--- /dev/null
+++ b/x-pack/plugins/monitoring/public/lib/jquery_flot/flot-charts/jquery.flot.image.js
@@ -0,0 +1,241 @@
+/* Flot plugin for plotting images.
+
+Copyright (c) 2007-2014 IOLA and Ole Laursen.
+Licensed under the MIT license.
+
+The data syntax is [ [ image, x1, y1, x2, y2 ], ... ] where (x1, y1) and
+(x2, y2) are where you intend the two opposite corners of the image to end up
+in the plot. Image must be a fully loaded JavaScript image (you can make one
+with new Image()). If the image is not complete, it's skipped when plotting.
+
+There are two helpers included for retrieving images. The easiest work the way
+that you put in URLs instead of images in the data, like this:
+
+ [ "myimage.png", 0, 0, 10, 10 ]
+
+Then call $.plot.image.loadData( data, options, callback ) where data and
+options are the same as you pass in to $.plot. This loads the images, replaces
+the URLs in the data with the corresponding images and calls "callback" when
+all images are loaded (or failed loading). In the callback, you can then call
+$.plot with the data set. See the included example.
+
+A more low-level helper, $.plot.image.load(urls, callback) is also included.
+Given a list of URLs, it calls callback with an object mapping from URL to
+Image object when all images are loaded or have failed loading.
+
+The plugin supports these options:
+
+ series: {
+ images: {
+ show: boolean
+ anchor: "corner" or "center"
+ alpha: [ 0, 1 ]
+ }
+ }
+
+They can be specified for a specific series:
+
+ $.plot( $("#placeholder"), [{
+ data: [ ... ],
+ images: { ... }
+ ])
+
+Note that because the data format is different from usual data points, you
+can't use images with anything else in a specific data series.
+
+Setting "anchor" to "center" causes the pixels in the image to be anchored at
+the corner pixel centers inside of at the pixel corners, effectively letting
+half a pixel stick out to each side in the plot.
+
+A possible future direction could be support for tiling for large images (like
+Google Maps).
+
+*/
+
+(function ($) {
+ var options = {
+ series: {
+ images: {
+ show: false,
+ alpha: 1,
+ anchor: "corner" // or "center"
+ }
+ }
+ };
+
+ $.plot.image = {};
+
+ $.plot.image.loadDataImages = function (series, options, callback) {
+ var urls = [], points = [];
+
+ var defaultShow = options.series.images.show;
+
+ $.each(series, function (i, s) {
+ if (!(defaultShow || s.images.show))
+ return;
+
+ if (s.data)
+ s = s.data;
+
+ $.each(s, function (i, p) {
+ if (typeof p[0] == "string") {
+ urls.push(p[0]);
+ points.push(p);
+ }
+ });
+ });
+
+ $.plot.image.load(urls, function (loadedImages) {
+ $.each(points, function (i, p) {
+ var url = p[0];
+ if (loadedImages[url])
+ p[0] = loadedImages[url];
+ });
+
+ callback();
+ });
+ }
+
+ $.plot.image.load = function (urls, callback) {
+ var missing = urls.length, loaded = {};
+ if (missing == 0)
+ callback({});
+
+ $.each(urls, function (i, url) {
+ var handler = function () {
+ --missing;
+
+ loaded[url] = this;
+
+ if (missing == 0)
+ callback(loaded);
+ };
+
+ $('').load(handler).error(handler).attr('src', url);
+ });
+ };
+
+ function drawSeries(plot, ctx, series) {
+ var plotOffset = plot.getPlotOffset();
+
+ if (!series.images || !series.images.show)
+ return;
+
+ var points = series.datapoints.points,
+ ps = series.datapoints.pointsize;
+
+ for (var i = 0; i < points.length; i += ps) {
+ var img = points[i],
+ x1 = points[i + 1], y1 = points[i + 2],
+ x2 = points[i + 3], y2 = points[i + 4],
+ xaxis = series.xaxis, yaxis = series.yaxis,
+ tmp;
+
+ // actually we should check img.complete, but it
+ // appears to be a somewhat unreliable indicator in
+ // IE6 (false even after load event)
+ if (!img || img.width <= 0 || img.height <= 0)
+ continue;
+
+ if (x1 > x2) {
+ tmp = x2;
+ x2 = x1;
+ x1 = tmp;
+ }
+ if (y1 > y2) {
+ tmp = y2;
+ y2 = y1;
+ y1 = tmp;
+ }
+
+ // if the anchor is at the center of the pixel, expand the
+ // image by 1/2 pixel in each direction
+ if (series.images.anchor == "center") {
+ tmp = 0.5 * (x2-x1) / (img.width - 1);
+ x1 -= tmp;
+ x2 += tmp;
+ tmp = 0.5 * (y2-y1) / (img.height - 1);
+ y1 -= tmp;
+ y2 += tmp;
+ }
+
+ // clip
+ if (x1 == x2 || y1 == y2 ||
+ x1 >= xaxis.max || x2 <= xaxis.min ||
+ y1 >= yaxis.max || y2 <= yaxis.min)
+ continue;
+
+ var sx1 = 0, sy1 = 0, sx2 = img.width, sy2 = img.height;
+ if (x1 < xaxis.min) {
+ sx1 += (sx2 - sx1) * (xaxis.min - x1) / (x2 - x1);
+ x1 = xaxis.min;
+ }
+
+ if (x2 > xaxis.max) {
+ sx2 += (sx2 - sx1) * (xaxis.max - x2) / (x2 - x1);
+ x2 = xaxis.max;
+ }
+
+ if (y1 < yaxis.min) {
+ sy2 += (sy1 - sy2) * (yaxis.min - y1) / (y2 - y1);
+ y1 = yaxis.min;
+ }
+
+ if (y2 > yaxis.max) {
+ sy1 += (sy1 - sy2) * (yaxis.max - y2) / (y2 - y1);
+ y2 = yaxis.max;
+ }
+
+ x1 = xaxis.p2c(x1);
+ x2 = xaxis.p2c(x2);
+ y1 = yaxis.p2c(y1);
+ y2 = yaxis.p2c(y2);
+
+ // the transformation may have swapped us
+ if (x1 > x2) {
+ tmp = x2;
+ x2 = x1;
+ x1 = tmp;
+ }
+ if (y1 > y2) {
+ tmp = y2;
+ y2 = y1;
+ y1 = tmp;
+ }
+
+ tmp = ctx.globalAlpha;
+ ctx.globalAlpha *= series.images.alpha;
+ ctx.drawImage(img,
+ sx1, sy1, sx2 - sx1, sy2 - sy1,
+ x1 + plotOffset.left, y1 + plotOffset.top,
+ x2 - x1, y2 - y1);
+ ctx.globalAlpha = tmp;
+ }
+ }
+
+ function processRawData(plot, series, data, datapoints) {
+ if (!series.images.show)
+ return;
+
+ // format is Image, x1, y1, x2, y2 (opposite corners)
+ datapoints.format = [
+ { required: true },
+ { x: true, number: true, required: true },
+ { y: true, number: true, required: true },
+ { x: true, number: true, required: true },
+ { y: true, number: true, required: true }
+ ];
+ }
+
+ function init(plot) {
+ plot.hooks.processRawData.push(processRawData);
+ plot.hooks.drawSeries.push(drawSeries);
+ }
+
+ $.plot.plugins.push({
+ init: init,
+ options: options,
+ name: 'image',
+ version: '1.1'
+ });
+})(jQuery);
diff --git a/x-pack/plugins/monitoring/public/lib/jquery_flot/flot-charts/jquery.flot.js b/x-pack/plugins/monitoring/public/lib/jquery_flot/flot-charts/jquery.flot.js
new file mode 100644
index 0000000000000..43db1cc3d93db
--- /dev/null
+++ b/x-pack/plugins/monitoring/public/lib/jquery_flot/flot-charts/jquery.flot.js
@@ -0,0 +1,3168 @@
+/* JavaScript plotting library for jQuery, version 0.8.3.
+
+Copyright (c) 2007-2014 IOLA and Ole Laursen.
+Licensed under the MIT license.
+
+*/
+
+// first an inline dependency, jquery.colorhelpers.js, we inline it here
+// for convenience
+
+/* Plugin for jQuery for working with colors.
+ *
+ * Version 1.1.
+ *
+ * Inspiration from jQuery color animation plugin by John Resig.
+ *
+ * Released under the MIT license by Ole Laursen, October 2009.
+ *
+ * Examples:
+ *
+ * $.color.parse("#fff").scale('rgb', 0.25).add('a', -0.5).toString()
+ * var c = $.color.extract($("#mydiv"), 'background-color');
+ * console.log(c.r, c.g, c.b, c.a);
+ * $.color.make(100, 50, 25, 0.4).toString() // returns "rgba(100,50,25,0.4)"
+ *
+ * Note that .scale() and .add() return the same modified object
+ * instead of making a new one.
+ *
+ * V. 1.1: Fix error handling so e.g. parsing an empty string does
+ * produce a color rather than just crashing.
+ */
+(function($){$.color={};$.color.make=function(r,g,b,a){var o={};o.r=r||0;o.g=g||0;o.b=b||0;o.a=a!=null?a:1;o.add=function(c,d){for(var i=0;i