diff --git a/.i18nrc.json b/.i18nrc.json index 650182830796f..fb3d83f561d7b 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -3,6 +3,7 @@ "common.ui": "src/ui", "server": "src/server", "console": "src/legacy/core_plugins/console", + "core": "src/core", "inputControl": "src/legacy/core_plugins/input_control_vis", "inspectorViews": "src/legacy/core_plugins/inspector_views", "interpreter": "src/legacy/core_plugins/interpreter", @@ -45,6 +46,7 @@ "xpack.watcher": "x-pack/plugins/watcher" }, "exclude": [ + "src/core/public/fatal_errors/get_error_info.ts", "src/ui/ui_render/bootstrap/app_bootstrap.js", "src/ui/ui_render/ui_render_mixin.js", "x-pack/plugins/infra/public/graphql/types.ts", diff --git a/docs/api/saved-objects/bulk_create.asciidoc b/docs/api/saved-objects/bulk_create.asciidoc index 62b63674e6f81..1e749cb67c472 100644 --- a/docs/api/saved-objects/bulk_create.asciidoc +++ b/docs/api/saved-objects/bulk_create.asciidoc @@ -33,6 +33,9 @@ contains the following properties: `attributes` (required):: (object) The data to persist +`references` (optional):: + (array) An array of objects with `name`, `id`, and `type` properties that describe the other saved objects this object references. The `name` can be used in the attributes to refer to the other saved object, but never the `id`, which may be updated automatically in the future during migrations or import/export. + `version` (optional):: (number) Enables specifying a version diff --git a/docs/api/saved-objects/create.asciidoc b/docs/api/saved-objects/create.asciidoc index 7631dd296c2b3..c4a2cf260f7d9 100644 --- a/docs/api/saved-objects/create.asciidoc +++ b/docs/api/saved-objects/create.asciidoc @@ -33,6 +33,8 @@ Note: You cannot access this endpoint via the Console in Kibana. `attributes` (required):: (object) The data to persist +`references` (optional):: + (array) An array of objects with `name`, `id`, and `type` properties that describe the other saved objects this object references. The `name` can be used in the attributes to refer to the other saved object, but never the `id`, which may be updated automatically in the future during migrations or import/export. ==== Examples diff --git a/docs/api/saved-objects/find.asciidoc b/docs/api/saved-objects/find.asciidoc index 8a866c9de71d7..43fea5b5116c6 100644 --- a/docs/api/saved-objects/find.asciidoc +++ b/docs/api/saved-objects/find.asciidoc @@ -29,6 +29,8 @@ Note: You cannot access this endpoint via the Console in Kibana. (array|string) The fields to return in the response `sort_field` (optional):: (string) The field on which the response will be sorted +`has_reference` (optional):: + (object) Filters to objects having a relationship with the type and id combination [NOTE] ============================================== diff --git a/docs/api/saved-objects/update.asciidoc b/docs/api/saved-objects/update.asciidoc index 2c61f2e66b4b0..092128040d3eb 100644 --- a/docs/api/saved-objects/update.asciidoc +++ b/docs/api/saved-objects/update.asciidoc @@ -26,6 +26,8 @@ Note: You cannot access this endpoint via the Console in Kibana. `attributes` (required):: (object) The data to persist +`references` (optional):: + (array) An array of objects with `name`, `id`, and `type` properties that describe the other saved objects this object references. The `name` can be used in the attributes to refer to the other saved object, but never the `id`, which may be updated automatically in the future during migrations or import/export. ==== Examples diff --git a/package.json b/package.json index ca5bdedd29744..d9b0b8fbe051a 100644 --- a/package.json +++ b/package.json @@ -95,7 +95,7 @@ }, "dependencies": { "@elastic/datemath": "5.0.2", - "@elastic/eui": "6.7.2", + "@elastic/eui": "6.7.4", "@elastic/filesaver": "1.1.2", "@elastic/good": "8.1.1-kibana2", "@elastic/numeral": "2.3.2", @@ -411,4 +411,4 @@ "node": "10.14.1", "yarn": "^1.10.1" } -} +} \ No newline at end of file diff --git a/packages/kbn-es/src/install/source.js b/packages/kbn-es/src/install/source.js index 32e5267129926..4f0fabec6a25a 100644 --- a/packages/kbn-es/src/install/source.js +++ b/packages/kbn-es/src/install/source.js @@ -20,6 +20,7 @@ const execa = require('execa'); const path = require('path'); const fs = require('fs'); +const os = require('os'); const readline = require('readline'); const chalk = require('chalk'); const crypto = require('crypto'); @@ -27,7 +28,7 @@ const simpleGit = require('simple-git/promise'); const { installArchive } = require('./archive'); const { createCliError } = require('../errors'); const { findMostRecentlyChanged, log: defaultLog, cache } = require('../utils'); -const { GRADLE_BIN, ES_ARCHIVE_PATTERN, ES_OSS_ARCHIVE_PATTERN, BASE_PATH } = require('../paths'); +const { GRADLE_BIN, BASE_PATH } = require('../paths'); const onceEvent = (emitter, event) => new Promise(resolve => emitter.once(event, resolve)); @@ -89,6 +90,7 @@ async function sourceInfo(cwd, license, log = defaultLog) { const git = simpleGit(cwd); + const { task, ext } = archiveForPlatform(os.platform(), license); const status = await git.status(); const branch = status.current; const sha = (await git.revparse(['HEAD'])).trim(); @@ -110,8 +112,8 @@ async function sourceInfo(cwd, license, log = defaultLog) { .digest('hex') .substr(0, 8); - const basename = `${branch}${license === 'oss' ? '-oss-' : '-'}${cwdHash}`; - const filename = `${basename}.tar.gz`; + const basename = `${branch}-${task}-${cwdHash}`; + const filename = `${basename}.${ext}`; return { etag: etag.digest('hex'), @@ -129,10 +131,18 @@ async function sourceInfo(cwd, license, log = defaultLog) { * @property {String} options.sourcePath * @property {ToolingLog} options.log * @returns {Object} containing archive and optional plugins + * + * Gradle tasks: + * :distribution:archives:darwin-tar:assemble + * :distribution:archives:linux-tar:assemble + * :distribution:archives:windows-zip:assemble + * :distribution:archives:oss-darwin-tar:assemble + * :distribution:archives:oss-linux-tar:assemble + * :distribution:archives:oss-windows-zip:assemble */ async function createSnapshot({ license, sourcePath, log = defaultLog }) { - const tarTask = license === 'oss' ? 'oss-tar' : 'tar'; - const buildArgs = [`:distribution:archives:${tarTask}:assemble`]; + const { task, ext } = archiveForPlatform(os.platform(), license); + const buildArgs = [`:distribution:archives:${task}:assemble`]; log.info('%s %s', GRADLE_BIN, buildArgs.join(' ')); @@ -157,12 +167,27 @@ async function createSnapshot({ license, sourcePath, log = defaultLog }) { throw createCliError('unable to build ES'); } - const archivePattern = license === 'oss' ? ES_OSS_ARCHIVE_PATTERN : ES_ARCHIVE_PATTERN; - const esTarballPath = findMostRecentlyChanged(path.resolve(sourcePath, archivePattern)); + const archivePattern = `distribution/archives/${task}/build/distributions/elasticsearch-*.${ext}`; + const esArchivePath = findMostRecentlyChanged(path.resolve(sourcePath, archivePattern)); - if (!esTarballPath) { + if (!esArchivePath) { throw createCliError('could not locate ES distribution'); } - return esTarballPath; + return esArchivePath; +} + +function archiveForPlatform(platform, license) { + const taskPrefix = license === 'oss' ? 'oss-' : ''; + + switch (platform) { + case 'darwin': + return { format: 'tar', ext: 'tar.gz', task: `${taskPrefix}darwin-tar`, platform: 'darwin' }; + case 'win32': + return { format: 'zip', ext: 'zip', task: `${taskPrefix}windows-zip`, platform: 'windows' }; + case 'linux': + return { format: 'tar', ext: 'tar.gz', task: `${taskPrefix}linux-tar`, platform: 'linux' }; + default: + throw new Error(`unknown platform: ${platform}`); + } } diff --git a/packages/kbn-es/src/paths.js b/packages/kbn-es/src/paths.js index 4c911bc5e6bf1..b0c1b9c1e6fe7 100644 --- a/packages/kbn-es/src/paths.js +++ b/packages/kbn-es/src/paths.js @@ -33,8 +33,3 @@ exports.ES_BIN = useBat('bin/elasticsearch'); exports.ES_CONFIG = 'config/elasticsearch.yml'; exports.ES_KEYSTORE_BIN = useBat('./bin/elasticsearch-keystore'); - -exports.ES_ARCHIVE_PATTERN = - 'distribution/archives/tar/build/distributions/elasticsearch-*-SNAPSHOT.tar.gz'; -exports.ES_OSS_ARCHIVE_PATTERN = - 'distribution/archives/oss-tar/build/distributions/elasticsearch-*-SNAPSHOT.tar.gz'; diff --git a/packages/kbn-i18n/package.json b/packages/kbn-i18n/package.json index af7485773acb9..4cdba0ac1317a 100644 --- a/packages/kbn-i18n/package.json +++ b/packages/kbn-i18n/package.json @@ -12,19 +12,19 @@ "kbn:watch": "node scripts/build --watch --source-maps" }, "devDependencies": { - "@babel/cli": "^7.1.0", - "@babel/core": "^7.1.0", - "@babel/plugin-proposal-class-properties": "^7.1.0", - "@babel/plugin-proposal-object-rest-spread": "^7.0.0", - "@babel/preset-env": "^7.1.0", + "@babel/cli": "^7.2.3", + "@babel/core": "^7.2.2", + "@babel/plugin-proposal-class-properties": "^7.3.0", + "@babel/plugin-proposal-object-rest-spread": "^7.3.1", + "@babel/preset-env": "^7.3.1", "@babel/preset-react": "^7.0.0", "@babel/preset-typescript": "^7.1.0", "@kbn/dev-utils": "1.0.0", "@types/intl-relativeformat": "^2.1.0", - "@types/react-intl": "^2.3.11", + "@types/react-intl": "^2.3.15", "del": "^3.0.0", "getopts": "^2.2.3", - "supports-color": "^5.5.0", + "supports-color": "^6.1.0", "typescript": "^3.0.3" }, "dependencies": { @@ -32,7 +32,7 @@ "intl-messageformat": "^2.2.0", "intl-relativeformat": "^2.1.0", "prop-types": "^15.6.2", - "react": "^16.3.0", - "react-intl": "^2.7.0" + "react": "^16.6.0", + "react-intl": "^2.8.0" } } diff --git a/packages/kbn-interpreter/src/public/socket.js b/packages/kbn-interpreter/src/public/socket.js index 7d209e09c6887..04f8a91ed01b8 100644 --- a/packages/kbn-interpreter/src/public/socket.js +++ b/packages/kbn-interpreter/src/public/socket.js @@ -26,7 +26,7 @@ export async function createSocket(basePath, functionsRegistry) { return new Promise((resolve, reject) => { const socket = io({ path: `${basePath}/socket.io`, - transports: ['polling', 'websocket'], + transports: ['polling'], transportOptions: { polling: { extraHeaders: { diff --git a/src/core/public/fatal_errors/__snapshots__/fatal_errors_screen.test.tsx.snap b/src/core/public/fatal_errors/__snapshots__/fatal_errors_screen.test.tsx.snap index 1e280f1dedf07..531ce175cfd61 100644 --- a/src/core/public/fatal_errors/__snapshots__/fatal_errors_screen.test.tsx.snap +++ b/src/core/public/fatal_errors/__snapshots__/fatal_errors_screen.test.tsx.snap @@ -28,7 +28,11 @@ exports[`rendering render matches snapshot 1`] = ` onClick={[Function]} type="button" > - Clear your session + , - Go back + , ] } body={

- Try refreshing the page. If that doesn't work, go back to the previous page or clear your session data. +

} iconColor="danger" iconType="alert" title={

- Something went wrong +

} /> diff --git a/src/core/public/fatal_errors/__snapshots__/fatal_errors_service.test.ts.snap b/src/core/public/fatal_errors/__snapshots__/fatal_errors_service.test.ts.snap index 841ba0f148aed..9210ca50fbe8c 100644 --- a/src/core/public/fatal_errors/__snapshots__/fatal_errors_service.test.ts.snap +++ b/src/core/public/fatal_errors/__snapshots__/fatal_errors_service.test.ts.snap @@ -3,11 +3,13 @@ exports[`#add() deletes all children of rootDomElement and renders into it: fatal error screen component 1`] = ` Array [ Array [ - , + + + ,
, ], ] diff --git a/src/core/public/fatal_errors/fatal_errors_screen.test.tsx b/src/core/public/fatal_errors/fatal_errors_screen.test.tsx index 3165162db8519..89406baa35f3c 100644 --- a/src/core/public/fatal_errors/fatal_errors_screen.test.tsx +++ b/src/core/public/fatal_errors/fatal_errors_screen.test.tsx @@ -19,9 +19,9 @@ import { EuiCallOut } from '@elastic/eui'; import testSubjSelector from '@kbn/test-subj-selector'; -import { mount, shallow } from 'enzyme'; import React from 'react'; import * as Rx from 'rxjs'; +import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers'; import { FatalErrorsScreen } from './fatal_errors_screen'; @@ -54,7 +54,7 @@ describe('reloading', () => { const locationReloadSpy = jest.spyOn(window.location, 'reload').mockImplementation(noop); - shallow(); + shallowWithIntl(); expect(addEventListenerSpy).toHaveBeenCalledTimes(1); expect(addEventListenerSpy).toHaveBeenCalledWith('hashchange', expect.any(Function), undefined); @@ -67,13 +67,13 @@ describe('reloading', () => { describe('rendering', () => { it('render matches snapshot', () => { - expect(shallow()).toMatchSnapshot(); + expect(shallowWithIntl()).toMatchSnapshot(); }); it('rerenders when errorInfo$ emits more errors', () => { const errorInfo$ = new Rx.ReplaySubject(); - const el = shallow(); + const el = shallowWithIntl(); expect(el.find(EuiCallOut)).toHaveLength(0); @@ -111,7 +111,7 @@ describe('buttons', () => { window.location.hash = '/foo/bar'; jest.spyOn(window.location, 'reload').mockImplementation(noop); - const el = mount(); + const el = mountWithIntl(); const button = el.find('button').find(testSubjSelector('clearSession')); button.simulate('click'); @@ -126,7 +126,7 @@ describe('buttons', () => { it('calls window.history.back()', () => { jest.spyOn(window.history, 'back').mockImplementation(noop); - const el = mount(); + const el = mountWithIntl(); const button = el.find('button').find(testSubjSelector('goBack')); button.simulate('click'); diff --git a/src/core/public/fatal_errors/fatal_errors_screen.tsx b/src/core/public/fatal_errors/fatal_errors_screen.tsx index ccd87fdf8e6c2..a139e501e44aa 100644 --- a/src/core/public/fatal_errors/fatal_errors_screen.tsx +++ b/src/core/public/fatal_errors/fatal_errors_screen.tsx @@ -31,6 +31,8 @@ import React from 'react'; import * as Rx from 'rxjs'; import { tap } from 'rxjs/operators'; +import { FormattedMessage } from '@kbn/i18n/react'; + import { ErrorInfo } from './get_error_info'; interface Props { @@ -91,11 +93,21 @@ export class FatalErrorsScreen extends React.Component { Something went wrong} + title={ +

+ +

+ } body={

- Try refreshing the page. If that doesn't work, go back to the previous page or - clear your session data. +

} actions={[ @@ -105,10 +117,16 @@ export class FatalErrorsScreen extends React.Component { onClick={this.onClickClearSession} data-test-subj="clearSession" > - Clear your session + , - Go back + , ]} /> diff --git a/src/core/public/fatal_errors/fatal_errors_service.tsx b/src/core/public/fatal_errors/fatal_errors_service.tsx index 942d05c41a51c..c4a5478d42a3c 100644 --- a/src/core/public/fatal_errors/fatal_errors_service.tsx +++ b/src/core/public/fatal_errors/fatal_errors_service.tsx @@ -26,6 +26,8 @@ import { InjectedMetadataService } from '../injected_metadata'; import { FatalErrorsScreen } from './fatal_errors_screen'; import { ErrorInfo, getErrorInfo } from './get_error_info'; +import { I18nProvider } from '@kbn/i18n/react'; + export interface FatalErrorsParams { rootDomElement: HTMLElement; injectedMetadata: InjectedMetadataService; @@ -85,11 +87,13 @@ export class FatalErrorsService { this.params.rootDomElement.appendChild(container); render( - , + + + , container ); } diff --git a/src/core/public/ui_settings/ui_settings_service.ts b/src/core/public/ui_settings/ui_settings_service.ts index 9d791b2e54f1b..131270adbb215 100644 --- a/src/core/public/ui_settings/ui_settings_service.ts +++ b/src/core/public/ui_settings/ui_settings_service.ts @@ -25,6 +25,8 @@ import { NotificationsStartContract } from '../notifications'; import { UiSettingsApi } from './ui_settings_api'; import { UiSettingsClient } from './ui_settings_client'; +import { i18n } from '@kbn/i18n'; + interface Deps { notifications: NotificationsStartContract; loadingCount: LoadingCountStartContract; @@ -47,7 +49,9 @@ export class UiSettingsService { api: this.uiSettingsApi, onUpdateError: error => { notifications.toasts.addDanger({ - title: 'Unable to update UI setting', + title: i18n.translate('core.uiSettings.unableUpdateUISettingNotificationMessageTitle', { + defaultMessage: 'Unable to update UI setting', + }), text: error.message, }); }, diff --git a/src/dev/i18n/README.md b/src/dev/i18n/README.md index 5f629617dfc66..8280f2b81a5a0 100644 --- a/src/dev/i18n/README.md +++ b/src/dev/i18n/README.md @@ -168,3 +168,23 @@ The tool generates a JSON/JSON5 file only if `--output` path is provided. It con } } ``` + +## Locale files verification / integration tool + +### Description + +The tool is used for verifying locale file, finding unused / missing messages, key duplications, grouping messages by namespaces and creating JSON files in right folders. + +### Notes + +The tool throws an exception if `formats` object is missing in locale file. + +### Usage + +```bash +node scripts/i18n_integrate --path path/to/locale.json +``` + +### Output + +The tool generates locale files in plugin folders and few other special locations based on namespaces and corresponding mappings defined in [.i18nrc.json](../../../.i18nrc.json). diff --git a/src/legacy/core_plugins/interpreter/public/interpreter.test.js b/src/legacy/core_plugins/interpreter/public/interpreter.test.js new file mode 100644 index 0000000000000..4408894f30a58 --- /dev/null +++ b/src/legacy/core_plugins/interpreter/public/interpreter.test.js @@ -0,0 +1,78 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { getInterpreter } from './interpreter'; + +import { createSocket } from '@kbn/interpreter/public'; +import { functionsRegistry } from './functions_registry'; + +jest.mock('@kbn/interpreter/public', () => ({ + createSocket: jest.fn(), + initializeInterpreter: jest.fn(() => Promise.resolve()), + loadBrowserRegistries: jest.fn(() => Promise.resolve()) +})); + +jest.mock('ui/chrome', () => ({ + getBasePath: jest.fn(() => '/abc/s/123'), + getInjected: jest.fn(config => { // eslint-disable-line no-unused-vars + return config === 'serverBasePath' ? '/abc' : '/123'; + }), +})); + +jest.mock('./functions', () => ({ + functions: [jest.fn()] +})); + +jest.mock('./render_functions_registry', () => ({ + renderFunctionsRegistry: { + register: jest.fn() + } +})); + +jest.mock('./renderers/visualization', () => ({ + visualization: jest.fn() +})); + +jest.mock('./functions_registry', () => ({ + functionsRegistry: { + register: jest.fn() + } +})); + +jest.mock('./types_registry', () => ({ + typesRegistry: jest.fn() +})); + +describe('Core Interpreter', () => { + + beforeEach(() => { + jest.resetModules(); + }); + + describe('getInterpreter', () => { + + it('calls createSocket with the correct arguments', async () => { + await getInterpreter(); + expect(createSocket).toHaveBeenCalledTimes(1); + expect(createSocket).toHaveBeenCalledWith('/abc', functionsRegistry); + }); + + }); + +}); diff --git a/src/legacy/core_plugins/interpreter/server/routes/socket.js b/src/legacy/core_plugins/interpreter/server/routes/socket.js index 6201eaf70ca8a..66e0fa782b449 100644 --- a/src/legacy/core_plugins/interpreter/server/routes/socket.js +++ b/src/legacy/core_plugins/interpreter/server/routes/socket.js @@ -46,7 +46,10 @@ export function socketApi(server) { handler: () => 'pong', }); - const io = socket(server.listener, { path: '/socket.io' }); + const io = socket(server.listener, { + path: '/socket.io', + transports: ['polling'], + }); io.on('connection', async socket => { // 'request' is the modified hapi request object diff --git a/src/legacy/core_plugins/kibana/mappings.json b/src/legacy/core_plugins/kibana/mappings.json index 155742b15c8a7..243d49c448c6d 100644 --- a/src/legacy/core_plugins/kibana/mappings.json +++ b/src/legacy/core_plugins/kibana/mappings.json @@ -42,7 +42,7 @@ } } }, - "savedSearchId": { + "savedSearchRefName": { "type": "keyword" }, "title": { diff --git a/src/legacy/core_plugins/kibana/migrations.js b/src/legacy/core_plugins/kibana/migrations.js index 8dee9566f0e8a..1d672936b389f 100644 --- a/src/legacy/core_plugins/kibana/migrations.js +++ b/src/legacy/core_plugins/kibana/migrations.js @@ -19,9 +19,53 @@ import { cloneDeep, get, omit } from 'lodash'; +function migrateIndexPattern(doc) { + const searchSourceJSON = get(doc, 'attributes.kibanaSavedObjectMeta.searchSourceJSON'); + if (typeof searchSourceJSON !== 'string') { + return; + } + let searchSource; + try { + searchSource = JSON.parse(searchSourceJSON); + } catch (e) { + // Let it go, the data is invalid and we'll leave it as is + return; + } + if (!searchSource.index) { + return; + } + doc.references.push({ + name: 'kibanaSavedObjectMeta.searchSourceJSON.index', + type: 'index-pattern', + id: searchSource.index, + }); + searchSource.indexRefName = 'kibanaSavedObjectMeta.searchSourceJSON.index'; + delete searchSource.index; + doc.attributes.kibanaSavedObjectMeta.searchSourceJSON = JSON.stringify(searchSource); +} + export const migrations = { visualization: { '7.0.0': (doc) => { + // Set new "references" attribute + doc.references = doc.references || []; + + // Migrate index pattern + migrateIndexPattern(doc); + + // Migrate saved search + const savedSearchId = get(doc, 'attributes.savedSearchId'); + if (savedSearchId) { + doc.references.push({ + type: 'search', + name: 'search_0', + id: savedSearchId, + }); + doc.attributes.savedSearchRefName = 'search_0'; + delete doc.attributes.savedSearchId; + } + + // Migrate table splits try { const visState = JSON.parse(doc.attributes.visState); if (get(visState, 'type') !== 'table') { @@ -55,5 +99,52 @@ export const migrations = { throw new Error(`Failure attempting to migrate saved object '${doc.attributes.title}' - ${e}`); } } - } + }, + dashboard: { + '7.0.0': (doc) => { + // Set new "references" attribute + doc.references = doc.references || []; + // Migrate index pattern + migrateIndexPattern(doc); + // Migrate panels + const panelsJSON = get(doc, 'attributes.panelsJSON'); + if (typeof panelsJSON !== 'string') { + return doc; + } + let panels; + try { + panels = JSON.parse(panelsJSON); + } catch (e) { + // Let it go, the data is invalid and we'll leave it as is + return doc; + } + if (!Array.isArray(panels)) { + return doc; + } + panels.forEach((panel, i) => { + if (!panel.type || !panel.id) { + return; + } + panel.panelRefName = `panel_${i}`; + doc.references.push({ + name: `panel_${i}`, + type: panel.type, + id: panel.id, + }); + delete panel.type; + delete panel.id; + }); + doc.attributes.panelsJSON = JSON.stringify(panels); + return doc; + }, + }, + search: { + '7.0.0': (doc) => { + // Set new "references" attribute + doc.references = doc.references || []; + // Migrate index pattern + migrateIndexPattern(doc); + return doc; + }, + }, }; diff --git a/src/legacy/core_plugins/kibana/migrations.test.js b/src/legacy/core_plugins/kibana/migrations.test.js index dc8ddb594af02..cafd141fc2f19 100644 --- a/src/legacy/core_plugins/kibana/migrations.test.js +++ b/src/legacy/core_plugins/kibana/migrations.test.js @@ -19,8 +19,7 @@ import { migrations } from './migrations'; -describe('table vis migrations', () => { - +describe('visualization', () => { describe('7.0.0', () => { const migrate = doc => migrations.visualization['7.0.0'](doc); const generateDoc = ({ type, aggs }) => ({ @@ -31,9 +30,296 @@ describe('table vis migrations', () => { uiStateJSON: '{}', version: 1, kibanaSavedObjectMeta: { - searchSourceJSON: '{}' - } - } + searchSourceJSON: '{}', + }, + }, + }); + + it('does not throw error on empty object', () => { + const migratedDoc = migrate({ + attributes: { + visState: '{}', + }, + }); + expect(migratedDoc).toMatchInlineSnapshot(` +Object { + "attributes": Object { + "visState": "{}", + }, + "references": Array [], +} +`); + }); + + it('skips errors when searchSourceJSON is null', () => { + const doc = { + id: '1', + type: 'visualization', + attributes: { + visState: '{}', + kibanaSavedObjectMeta: { + searchSourceJSON: null, + }, + savedSearchId: '123', + }, + }; + const migratedDoc = migrate(doc); + expect(migratedDoc).toMatchInlineSnapshot(` +Object { + "attributes": Object { + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": null, + }, + "savedSearchRefName": "search_0", + "visState": "{}", + }, + "id": "1", + "references": Array [ + Object { + "id": "123", + "name": "search_0", + "type": "search", + }, + ], + "type": "visualization", +} +`); + }); + + it('skips errors when searchSourceJSON is undefined', () => { + const doc = { + id: '1', + type: 'visualization', + attributes: { + visState: '{}', + kibanaSavedObjectMeta: { + searchSourceJSON: undefined, + }, + savedSearchId: '123', + }, + }; + const migratedDoc = migrate(doc); + expect(migratedDoc).toMatchInlineSnapshot(` +Object { + "attributes": Object { + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": undefined, + }, + "savedSearchRefName": "search_0", + "visState": "{}", + }, + "id": "1", + "references": Array [ + Object { + "id": "123", + "name": "search_0", + "type": "search", + }, + ], + "type": "visualization", +} +`); + }); + + it('skips error when searchSourceJSON is not a string', () => { + const doc = { + id: '1', + type: 'visualization', + attributes: { + visState: '{}', + kibanaSavedObjectMeta: { + searchSourceJSON: 123, + }, + savedSearchId: '123', + }, + }; + expect(migrate(doc)).toMatchInlineSnapshot(` +Object { + "attributes": Object { + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": 123, + }, + "savedSearchRefName": "search_0", + "visState": "{}", + }, + "id": "1", + "references": Array [ + Object { + "id": "123", + "name": "search_0", + "type": "search", + }, + ], + "type": "visualization", +} +`); + }); + + it('skips error when searchSourceJSON is invalid json', () => { + const doc = { + id: '1', + type: 'visualization', + attributes: { + visState: '{}', + kibanaSavedObjectMeta: { + searchSourceJSON: '{abc123}', + }, + savedSearchId: '123', + }, + }; + expect(migrate(doc)).toMatchInlineSnapshot(` +Object { + "attributes": Object { + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": "{abc123}", + }, + "savedSearchRefName": "search_0", + "visState": "{}", + }, + "id": "1", + "references": Array [ + Object { + "id": "123", + "name": "search_0", + "type": "search", + }, + ], + "type": "visualization", +} +`); + }); + + it('skips error when "index" is missing from searchSourceJSON', () => { + const doc = { + id: '1', + type: 'visualization', + attributes: { + visState: '{}', + kibanaSavedObjectMeta: { + searchSourceJSON: JSON.stringify({ bar: true }), + }, + savedSearchId: '123', + }, + }; + const migratedDoc = migrate(doc); + expect(migratedDoc).toMatchInlineSnapshot(` +Object { + "attributes": Object { + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": "{\\"bar\\":true}", + }, + "savedSearchRefName": "search_0", + "visState": "{}", + }, + "id": "1", + "references": Array [ + Object { + "id": "123", + "name": "search_0", + "type": "search", + }, + ], + "type": "visualization", +} +`); + }); + + it('extracts "index" attribute from doc', () => { + const doc = { + id: '1', + type: 'visualization', + attributes: { + visState: '{}', + kibanaSavedObjectMeta: { + searchSourceJSON: JSON.stringify({ bar: true, index: 'pattern*' }), + }, + savedSearchId: '123', + }, + }; + const migratedDoc = migrate(doc); + expect(migratedDoc).toMatchInlineSnapshot(` +Object { + "attributes": Object { + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": "{\\"bar\\":true,\\"indexRefName\\":\\"kibanaSavedObjectMeta.searchSourceJSON.index\\"}", + }, + "savedSearchRefName": "search_0", + "visState": "{}", + }, + "id": "1", + "references": Array [ + Object { + "id": "pattern*", + "name": "kibanaSavedObjectMeta.searchSourceJSON.index", + "type": "index-pattern", + }, + Object { + "id": "123", + "name": "search_0", + "type": "search", + }, + ], + "type": "visualization", +} +`); + }); + + it('skips extracting savedSearchId when missing', () => { + const doc = { + id: '1', + attributes: { + visState: '{}', + kibanaSavedObjectMeta: { + searchSourceJSON: '{}', + }, + }, + }; + const migratedDoc = migrate(doc); + expect(migratedDoc).toMatchInlineSnapshot(` +Object { + "attributes": Object { + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": "{}", + }, + "visState": "{}", + }, + "id": "1", + "references": Array [], +} +`); + }); + + it('extract savedSearchId from doc', () => { + const doc = { + id: '1', + attributes: { + visState: '{}', + kibanaSavedObjectMeta: { + searchSourceJSON: '{}', + }, + savedSearchId: '123', + }, + }; + const migratedDoc = migrate(doc); + expect(migratedDoc).toMatchInlineSnapshot(` +Object { + "attributes": Object { + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": "{}", + }, + "savedSearchRefName": "search_0", + "visState": "{}", + }, + "id": "1", + "references": Array [ + Object { + "id": "123", + "name": "search_0", + "type": "search", + }, + ], +} +`); }); it('should return a new object if vis is table and has multiple split aggs', () => { @@ -41,17 +327,17 @@ describe('table vis migrations', () => { { id: '1', schema: 'metric', - params: {} + params: {}, }, { id: '2', schema: 'split', - params: { foo: 'bar', row: true } + params: { foo: 'bar', row: true }, }, { id: '3', schema: 'split', - params: { hey: 'ya', row: false } + params: { hey: 'ya', row: false }, }, ]; const tableDoc = generateDoc({ type: 'table', aggs }); @@ -73,18 +359,18 @@ describe('table vis migrations', () => { { id: '1', schema: 'metric', - params: {} + params: {}, }, { id: '2', schema: 'split', - params: { foo: 'bar', row: true } + params: { foo: 'bar', row: true }, }, { id: '3', schema: 'segment', - params: { hey: 'ya' } - } + params: { hey: 'ya' }, + }, ]; const pieDoc = generateDoc({ type: 'pie', aggs }); const expected = pieDoc; @@ -97,13 +383,13 @@ describe('table vis migrations', () => { { id: '1', schema: 'metric', - params: {} + params: {}, }, { id: '2', schema: 'split', - params: { foo: 'bar', row: true } - } + params: { foo: 'bar', row: true }, + }, ]; const tableDoc = generateDoc({ type: 'table', aggs }); const expected = tableDoc; @@ -116,23 +402,23 @@ describe('table vis migrations', () => { { id: '1', schema: 'metric', - params: {} + params: {}, }, { id: '2', schema: 'split', - params: { foo: 'bar', row: true } + params: { foo: 'bar', row: true }, }, { id: '3', schema: 'split', - params: { hey: 'ya', row: false } + params: { hey: 'ya', row: false }, }, { id: '4', schema: 'bucket', - params: { heyyy: 'yaaa' } - } + params: { heyyy: 'yaaa' }, + }, ]; const expected = ['metric', 'split', 'bucket', 'bucket']; const migrated = migrate(generateDoc({ type: 'table', aggs })); @@ -145,18 +431,18 @@ describe('table vis migrations', () => { { id: '1', schema: 'metric', - params: {} + params: {}, }, { id: '2', schema: 'split', - params: { foo: 'bar', row: true } + params: { foo: 'bar', row: true }, }, { id: '3', schema: 'split', - params: { hey: 'ya', row: false } - } + params: { hey: 'ya', row: false }, + }, ]; const expected = [{}, { foo: 'bar', row: true }, { hey: 'ya' }]; const migrated = migrate(generateDoc({ type: 'table', aggs })); @@ -173,12 +459,555 @@ describe('table vis migrations', () => { uiStateJSON: '{}', version: 1, kibanaSavedObjectMeta: { - searchSourceJSON: '{}' - } - } + searchSourceJSON: '{}', + }, + }, }; expect(() => migrate(doc)).toThrowError(/My Vis/); }); }); +}); + +describe('dashboard', () => { + describe('7.0.0', () => { + const migration = migrations.dashboard['7.0.0']; + + test('skips error on empty object', () => { + expect(migration({})).toMatchInlineSnapshot(` +Object { + "references": Array [], +} +`); + }); + + test('skips errors when searchSourceJSON is null', () => { + const doc = { + id: '1', + type: 'dashboard', + attributes: { + kibanaSavedObjectMeta: { + searchSourceJSON: null, + }, + panelsJSON: + '[{"id":"1","type":"visualization","foo":true},{"id":"2","type":"visualization","bar":true}]', + }, + }; + const migratedDoc = migration(doc); + expect(migratedDoc).toMatchInlineSnapshot(` +Object { + "attributes": Object { + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": null, + }, + "panelsJSON": "[{\\"foo\\":true,\\"panelRefName\\":\\"panel_0\\"},{\\"bar\\":true,\\"panelRefName\\":\\"panel_1\\"}]", + }, + "id": "1", + "references": Array [ + Object { + "id": "1", + "name": "panel_0", + "type": "visualization", + }, + Object { + "id": "2", + "name": "panel_1", + "type": "visualization", + }, + ], + "type": "dashboard", +} +`); + }); + + test('skips errors when searchSourceJSON is undefined', () => { + const doc = { + id: '1', + type: 'dashboard', + attributes: { + kibanaSavedObjectMeta: { + searchSourceJSON: undefined, + }, + panelsJSON: + '[{"id":"1","type":"visualization","foo":true},{"id":"2","type":"visualization","bar":true}]', + }, + }; + const migratedDoc = migration(doc); + expect(migratedDoc).toMatchInlineSnapshot(` +Object { + "attributes": Object { + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": undefined, + }, + "panelsJSON": "[{\\"foo\\":true,\\"panelRefName\\":\\"panel_0\\"},{\\"bar\\":true,\\"panelRefName\\":\\"panel_1\\"}]", + }, + "id": "1", + "references": Array [ + Object { + "id": "1", + "name": "panel_0", + "type": "visualization", + }, + Object { + "id": "2", + "name": "panel_1", + "type": "visualization", + }, + ], + "type": "dashboard", +} +`); + }); + + test('skips error when searchSourceJSON is not a string', () => { + const doc = { + id: '1', + type: 'dashboard', + attributes: { + kibanaSavedObjectMeta: { + searchSourceJSON: 123, + }, + panelsJSON: + '[{"id":"1","type":"visualization","foo":true},{"id":"2","type":"visualization","bar":true}]', + }, + }; + expect(migration(doc)).toMatchInlineSnapshot(` +Object { + "attributes": Object { + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": 123, + }, + "panelsJSON": "[{\\"foo\\":true,\\"panelRefName\\":\\"panel_0\\"},{\\"bar\\":true,\\"panelRefName\\":\\"panel_1\\"}]", + }, + "id": "1", + "references": Array [ + Object { + "id": "1", + "name": "panel_0", + "type": "visualization", + }, + Object { + "id": "2", + "name": "panel_1", + "type": "visualization", + }, + ], + "type": "dashboard", +} +`); + }); + + test('skips error when searchSourceJSON is invalid json', () => { + const doc = { + id: '1', + type: 'dashboard', + attributes: { + kibanaSavedObjectMeta: { + searchSourceJSON: '{abc123}', + }, + panelsJSON: + '[{"id":"1","type":"visualization","foo":true},{"id":"2","type":"visualization","bar":true}]', + }, + }; + expect(migration(doc)).toMatchInlineSnapshot(` +Object { + "attributes": Object { + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": "{abc123}", + }, + "panelsJSON": "[{\\"foo\\":true,\\"panelRefName\\":\\"panel_0\\"},{\\"bar\\":true,\\"panelRefName\\":\\"panel_1\\"}]", + }, + "id": "1", + "references": Array [ + Object { + "id": "1", + "name": "panel_0", + "type": "visualization", + }, + Object { + "id": "2", + "name": "panel_1", + "type": "visualization", + }, + ], + "type": "dashboard", +} +`); + }); + + test('skips error when "index" is missing from searchSourceJSON', () => { + const doc = { + id: '1', + type: 'dashboard', + attributes: { + kibanaSavedObjectMeta: { + searchSourceJSON: JSON.stringify({ bar: true }), + }, + panelsJSON: + '[{"id":"1","type":"visualization","foo":true},{"id":"2","type":"visualization","bar":true}]', + }, + }; + const migratedDoc = migration(doc); + expect(migratedDoc).toMatchInlineSnapshot(` +Object { + "attributes": Object { + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": "{\\"bar\\":true}", + }, + "panelsJSON": "[{\\"foo\\":true,\\"panelRefName\\":\\"panel_0\\"},{\\"bar\\":true,\\"panelRefName\\":\\"panel_1\\"}]", + }, + "id": "1", + "references": Array [ + Object { + "id": "1", + "name": "panel_0", + "type": "visualization", + }, + Object { + "id": "2", + "name": "panel_1", + "type": "visualization", + }, + ], + "type": "dashboard", +} +`); + }); + + test('extracts "index" attribute from doc', () => { + const doc = { + id: '1', + type: 'dashboard', + attributes: { + kibanaSavedObjectMeta: { + searchSourceJSON: JSON.stringify({ bar: true, index: 'pattern*' }), + }, + panelsJSON: + '[{"id":"1","type":"visualization","foo":true},{"id":"2","type":"visualization","bar":true}]', + }, + }; + const migratedDoc = migration(doc); + expect(migratedDoc).toMatchInlineSnapshot(` +Object { + "attributes": Object { + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": "{\\"bar\\":true,\\"indexRefName\\":\\"kibanaSavedObjectMeta.searchSourceJSON.index\\"}", + }, + "panelsJSON": "[{\\"foo\\":true,\\"panelRefName\\":\\"panel_0\\"},{\\"bar\\":true,\\"panelRefName\\":\\"panel_1\\"}]", + }, + "id": "1", + "references": Array [ + Object { + "id": "pattern*", + "name": "kibanaSavedObjectMeta.searchSourceJSON.index", + "type": "index-pattern", + }, + Object { + "id": "1", + "name": "panel_0", + "type": "visualization", + }, + Object { + "id": "2", + "name": "panel_1", + "type": "visualization", + }, + ], + "type": "dashboard", +} +`); + }); + + test('skips error when panelsJSON is not a string', () => { + const doc = { + id: '1', + attributes: { + panelsJSON: 123, + }, + }; + expect(migration(doc)).toMatchInlineSnapshot(` +Object { + "attributes": Object { + "panelsJSON": 123, + }, + "id": "1", + "references": Array [], +} +`); + }); + + test('skips error when panelsJSON is not valid JSON', () => { + const doc = { + id: '1', + attributes: { + panelsJSON: '{123abc}', + }, + }; + expect(migration(doc)).toMatchInlineSnapshot(` +Object { + "attributes": Object { + "panelsJSON": "{123abc}", + }, + "id": "1", + "references": Array [], +} +`); + }); + + test('skips panelsJSON when its not an array', () => { + const doc = { + id: '1', + attributes: { + panelsJSON: '{}', + }, + }; + expect(migration(doc)).toMatchInlineSnapshot(` +Object { + "attributes": Object { + "panelsJSON": "{}", + }, + "id": "1", + "references": Array [], +} +`); + }); + + test('skips error when a panel is missing "type" attribute', () => { + const doc = { + id: '1', + attributes: { + panelsJSON: '[{"id":"123"}]', + }, + }; + expect(migration(doc)).toMatchInlineSnapshot(` +Object { + "attributes": Object { + "panelsJSON": "[{\\"id\\":\\"123\\"}]", + }, + "id": "1", + "references": Array [], +} +`); + }); + + test('skips error when a panel is missing "id" attribute', () => { + const doc = { + id: '1', + attributes: { + panelsJSON: '[{"type":"visualization"}]', + }, + }; + expect(migration(doc)).toMatchInlineSnapshot(` +Object { + "attributes": Object { + "panelsJSON": "[{\\"type\\":\\"visualization\\"}]", + }, + "id": "1", + "references": Array [], +} +`); + }); + + test('extract panel references from doc', () => { + const doc = { + id: '1', + attributes: { + panelsJSON: + '[{"id":"1","type":"visualization","foo":true},{"id":"2","type":"visualization","bar":true}]', + }, + }; + const migratedDoc = migration(doc); + expect(migratedDoc).toMatchInlineSnapshot(` +Object { + "attributes": Object { + "panelsJSON": "[{\\"foo\\":true,\\"panelRefName\\":\\"panel_0\\"},{\\"bar\\":true,\\"panelRefName\\":\\"panel_1\\"}]", + }, + "id": "1", + "references": Array [ + Object { + "id": "1", + "name": "panel_0", + "type": "visualization", + }, + Object { + "id": "2", + "name": "panel_1", + "type": "visualization", + }, + ], +} +`); + }); + }); +}); + +describe('search', () => { + describe('7.0.0', () => { + const migration = migrations.search['7.0.0']; + test('skips errors when searchSourceJSON is null', () => { + const doc = { + id: '123', + type: 'search', + attributes: { + foo: true, + kibanaSavedObjectMeta: { + searchSourceJSON: null, + }, + }, + }; + const migratedDoc = migration(doc); + expect(migratedDoc).toMatchInlineSnapshot(` +Object { + "attributes": Object { + "foo": true, + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": null, + }, + }, + "id": "123", + "references": Array [], + "type": "search", +} +`); + }); + + test('skips errors when searchSourceJSON is undefined', () => { + const doc = { + id: '123', + type: 'search', + attributes: { + foo: true, + kibanaSavedObjectMeta: { + searchSourceJSON: undefined, + }, + }, + }; + const migratedDoc = migration(doc); + expect(migratedDoc).toMatchInlineSnapshot(` +Object { + "attributes": Object { + "foo": true, + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": undefined, + }, + }, + "id": "123", + "references": Array [], + "type": "search", +} +`); + }); + + test('skips error when searchSourceJSON is not a string', () => { + const doc = { + id: '123', + type: 'search', + attributes: { + foo: true, + kibanaSavedObjectMeta: { + searchSourceJSON: 123, + }, + }, + }; + expect(migration(doc)).toMatchInlineSnapshot(` +Object { + "attributes": Object { + "foo": true, + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": 123, + }, + }, + "id": "123", + "references": Array [], + "type": "search", +} +`); + }); + + test('skips error when searchSourceJSON is invalid json', () => { + const doc = { + id: '123', + type: 'search', + attributes: { + foo: true, + kibanaSavedObjectMeta: { + searchSourceJSON: '{abc123}', + }, + }, + }; + expect(migration(doc)).toMatchInlineSnapshot(` +Object { + "attributes": Object { + "foo": true, + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": "{abc123}", + }, + }, + "id": "123", + "references": Array [], + "type": "search", +} +`); + }); + + test('skips error when "index" is missing from searchSourceJSON', () => { + const doc = { + id: '123', + type: 'search', + attributes: { + foo: true, + kibanaSavedObjectMeta: { + searchSourceJSON: JSON.stringify({ bar: true }), + }, + }, + }; + const migratedDoc = migration(doc); + expect(migratedDoc).toMatchInlineSnapshot(` +Object { + "attributes": Object { + "foo": true, + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": "{\\"bar\\":true}", + }, + }, + "id": "123", + "references": Array [], + "type": "search", +} +`); + }); + + test('extracts "index" attribute from doc', () => { + const doc = { + id: '123', + type: 'search', + attributes: { + foo: true, + kibanaSavedObjectMeta: { + searchSourceJSON: JSON.stringify({ bar: true, index: 'pattern*' }), + }, + }, + }; + const migratedDoc = migration(doc); + expect(migratedDoc).toMatchInlineSnapshot(` +Object { + "attributes": Object { + "foo": true, + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": "{\\"bar\\":true,\\"indexRefName\\":\\"kibanaSavedObjectMeta.searchSourceJSON.index\\"}", + }, + }, + "id": "123", + "references": Array [ + Object { + "id": "pattern*", + "name": "kibanaSavedObjectMeta.searchSourceJSON.index", + "type": "index-pattern", + }, + ], + "type": "search", +} +`); + }); + }); }); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.js b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.js index ed3edbe6b69bb..959ebfc892bc6 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.js +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.js @@ -215,7 +215,7 @@ app.directive('dashboardApp', function ($injector) { dashboardStateManager.getPanels().find((panel) => panel.panelIndex === panelIndex); }; - $scope.updateQueryAndFetch = function (query) { + $scope.updateQueryAndFetch = function ({ query }) { const oldQuery = $scope.model.query; if (_.isEqual(oldQuery, query)) { // The user can still request a reload in the query bar, even if the @@ -236,7 +236,9 @@ app.directive('dashboardApp', function ($injector) { $scope.indexPatterns = dashboardStateManager.getPanelIndexPatterns(); }; - $scope.$watch('model.query', $scope.updateQueryAndFetch); + $scope.$watch('model.query', (query) => { + $scope.updateQueryAndFetch({ query }); + }); $scope.$listenAndDigestAsync(timefilter, 'fetch', () => { dashboardStateManager.handleTimeChange(timefilter.getTime()); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/saved_dashboard/saved_dashboard.js b/src/legacy/core_plugins/kibana/public/dashboard/saved_dashboard/saved_dashboard.js index 82cf20d9c25af..c3e4b2c668656 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/saved_dashboard/saved_dashboard.js +++ b/src/legacy/core_plugins/kibana/public/dashboard/saved_dashboard/saved_dashboard.js @@ -22,6 +22,10 @@ import { uiModules } from 'ui/modules'; import { createDashboardEditUrl } from '../dashboard_constants'; import { createLegacyClass } from 'ui/utils/legacy_class'; import { SavedObjectProvider } from 'ui/courier'; +import { + extractReferences, + injectReferences, +} from './saved_dashboard_references'; const module = uiModules.get('app/dashboard'); @@ -37,6 +41,8 @@ module.factory('SavedDashboard', function (Private, config, i18n) { type: SavedDashboard.type, mapping: SavedDashboard.mapping, searchSource: SavedDashboard.searchsource, + extractReferences: extractReferences, + injectReferences: injectReferences, // if this is null/undefined then the SavedObject will be assigned the defaults id: id, diff --git a/src/legacy/core_plugins/kibana/public/dashboard/saved_dashboard/saved_dashboard_references.js b/src/legacy/core_plugins/kibana/public/dashboard/saved_dashboard/saved_dashboard_references.js new file mode 100644 index 0000000000000..724847337a26e --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/dashboard/saved_dashboard/saved_dashboard_references.js @@ -0,0 +1,78 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export function extractReferences({ attributes, references = [] }) { + const panelReferences = []; + const panels = JSON.parse(attributes.panelsJSON); + panels.forEach((panel, i) => { + if (!panel.type) { + throw new Error(`"type" attribute is missing from panel "${i}"`); + } + if (!panel.id) { + throw new Error(`"id" attribute is missing from panel "${i}"`); + } + panel.panelRefName = `panel_${i}`; + panelReferences.push({ + name: `panel_${i}`, + type: panel.type, + id: panel.id, + }); + delete panel.type; + delete panel.id; + }); + return { + references: [ + ...references, + ...panelReferences, + ], + attributes: { + ...attributes, + panelsJSON: JSON.stringify(panels), + }, + }; +} + +export function injectReferences(savedObject, references) { + // Skip if panelsJSON is missing otherwise this will cause saved object import to fail when + // importing objects without panelsJSON. At development time of this, there is no guarantee each saved + // object has panelsJSON in all previous versions of kibana. + if (typeof savedObject.panelsJSON !== 'string') { + return; + } + const panels = JSON.parse(savedObject.panelsJSON); + // Same here, prevent failing saved object import if ever panels aren't an array. + if (!Array.isArray(panels)) { + return; + } + panels.forEach((panel) => { + if (!panel.panelRefName) { + return; + } + const reference = references.find(reference => reference.name === panel.panelRefName); + if (!reference) { + // Throw an error since "panelRefName" means the reference exists within + // "references" and in this scenario we have bad data. + throw new Error(`Could not find reference "${panel.panelRefName}"`); + } + panel.id = reference.id; + panel.type = reference.type; + delete panel.panelRefName; + }); + savedObject.panelsJSON = JSON.stringify(panels); +} diff --git a/src/legacy/core_plugins/kibana/public/dashboard/saved_dashboard/saved_dashboard_references.test.js b/src/legacy/core_plugins/kibana/public/dashboard/saved_dashboard/saved_dashboard_references.test.js new file mode 100644 index 0000000000000..f32effd667846 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/dashboard/saved_dashboard/saved_dashboard_references.test.js @@ -0,0 +1,220 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { extractReferences, injectReferences } from './saved_dashboard_references'; + +describe('extractReferences', () => { + test('extracts references from panelsJSON', () => { + const doc = { + id: '1', + attributes: { + foo: true, + panelsJSON: JSON.stringify([ + { + type: 'visualization', + id: '1', + title: 'Title 1', + }, + { + type: 'visualization', + id: '2', + title: 'Title 2', + }, + ]), + }, + }; + const updatedDoc = extractReferences(doc); + /* eslint-disable max-len */ + expect(updatedDoc).toMatchInlineSnapshot(` +Object { + "attributes": Object { + "foo": true, + "panelsJSON": "[{\\"title\\":\\"Title 1\\",\\"panelRefName\\":\\"panel_0\\"},{\\"title\\":\\"Title 2\\",\\"panelRefName\\":\\"panel_1\\"}]", + }, + "references": Array [ + Object { + "id": "1", + "name": "panel_0", + "type": "visualization", + }, + Object { + "id": "2", + "name": "panel_1", + "type": "visualization", + }, + ], +} +`); + /* eslint-enable max-len */ + }); + + test('fails when "type" attribute is missing from a panel', () => { + const doc = { + id: '1', + attributes: { + foo: true, + panelsJSON: JSON.stringify([ + { + id: '1', + title: 'Title 1', + }, + ]), + }, + }; + expect(() => extractReferences(doc)).toThrowErrorMatchingInlineSnapshot( + `"\\"type\\" attribute is missing from panel \\"0\\""` + ); + }); + + test('fails when "id" attribute is missing from a panel', () => { + const doc = { + id: '1', + attributes: { + foo: true, + panelsJSON: JSON.stringify([ + { + type: 'visualization', + title: 'Title 1', + }, + ]), + }, + }; + expect(() => extractReferences(doc)).toThrowErrorMatchingInlineSnapshot( + `"\\"id\\" attribute is missing from panel \\"0\\""` + ); + }); +}); + +describe('injectReferences', () => { + test('injects references into context', () => { + const context = { + id: '1', + foo: true, + panelsJSON: JSON.stringify([ + { + panelRefName: 'panel_0', + title: 'Title 1', + }, + { + panelRefName: 'panel_1', + title: 'Title 2', + }, + ]), + }; + const references = [ + { + name: 'panel_0', + type: 'visualization', + id: '1', + }, + { + name: 'panel_1', + type: 'visualization', + id: '2', + }, + ]; + injectReferences(context, references); + /* eslint-disable max-len */ + expect(context).toMatchInlineSnapshot(` +Object { + "foo": true, + "id": "1", + "panelsJSON": "[{\\"title\\":\\"Title 1\\",\\"id\\":\\"1\\",\\"type\\":\\"visualization\\"},{\\"title\\":\\"Title 2\\",\\"id\\":\\"2\\",\\"type\\":\\"visualization\\"}]", +} +`); + /* eslint-enable max-len */ + }); + + test('skips when panelsJSON is missing', () => { + const context = { + id: '1', + foo: true, + }; + injectReferences(context, []); + expect(context).toMatchInlineSnapshot(` +Object { + "foo": true, + "id": "1", +} +`); + }); + + test('skips when panelsJSON is not an array', () => { + const context = { + id: '1', + foo: true, + panelsJSON: '{}', + }; + injectReferences(context, []); + expect(context).toMatchInlineSnapshot(` +Object { + "foo": true, + "id": "1", + "panelsJSON": "{}", +} +`); + }); + + test('skips a panel when panelRefName is missing', () => { + const context = { + id: '1', + foo: true, + panelsJSON: JSON.stringify([ + { + panelRefName: 'panel_0', + title: 'Title 1', + }, + { + title: 'Title 2', + }, + ]), + }; + const references = [ + { + name: 'panel_0', + type: 'visualization', + id: '1', + }, + ]; + injectReferences(context, references); + expect(context).toMatchInlineSnapshot(` +Object { + "foo": true, + "id": "1", + "panelsJSON": "[{\\"title\\":\\"Title 1\\",\\"id\\":\\"1\\",\\"type\\":\\"visualization\\"},{\\"title\\":\\"Title 2\\"}]", +} +`); + }); + + test(`fails when it can't find the reference in the array`, () => { + const context = { + id: '1', + foo: true, + panelsJSON: JSON.stringify([ + { + panelRefName: 'panel_0', + title: 'Title 1', + }, + ]), + }; + expect(() => injectReferences(context, [])).toThrowErrorMatchingInlineSnapshot( + `"Could not find reference \\"panel_0\\""` + ); + }); +}); diff --git a/src/legacy/core_plugins/kibana/public/discover/controllers/discover.js b/src/legacy/core_plugins/kibana/public/discover/controllers/discover.js index cedaa5fbb232a..2602ff5e053cd 100644 --- a/src/legacy/core_plugins/kibana/public/discover/controllers/discover.js +++ b/src/legacy/core_plugins/kibana/public/discover/controllers/discover.js @@ -525,7 +525,9 @@ function discoverController( } }); - $scope.$watch('state.query', $scope.updateQueryAndFetch); + $scope.$watch('state.query', (query) => { + $scope.updateQueryAndFetch({ query }); + }); $scope.$watchMulti([ 'rows', @@ -643,7 +645,7 @@ function discoverController( .catch(notify.error); }; - $scope.updateQueryAndFetch = function (query) { + $scope.updateQueryAndFetch = function ({ query }) { $state.query = migrateLegacyQuery(query); $scope.fetch(); }; diff --git a/src/legacy/core_plugins/kibana/public/home/tutorial_resources/aws_metrics/screenshot.png b/src/legacy/core_plugins/kibana/public/home/tutorial_resources/aws_metrics/screenshot.png new file mode 100644 index 0000000000000..9d179622189a2 Binary files /dev/null and b/src/legacy/core_plugins/kibana/public/home/tutorial_resources/aws_metrics/screenshot.png differ diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/_view.js b/src/legacy/core_plugins/kibana/public/management/sections/objects/_view.js index 735d1f22daedf..6f513b46f18ec 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/_view.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/_view.js @@ -125,6 +125,14 @@ uiModules.get('apps/management') value: '{}' }); } + + if (!fieldMap.references) { + fields.push({ + name: 'references', + type: 'array', + value: '[]', + }); + } }; $scope.notFound = $routeParams.notFound; @@ -136,7 +144,10 @@ uiModules.get('apps/management') $scope.obj = obj; $scope.link = service.urlFor(obj.id); - const fields = _.reduce(obj.attributes, createField, []); + const fields = _.reduce(obj.attributes, createField, []); + // Special handling for references which isn't within "attributes" + createField(fields, obj.references, 'references'); + if (service.Class) readObjectClass(fields, service.Class); // sorts twice since we want numerical sort to prioritize over name, @@ -234,7 +245,9 @@ uiModules.get('apps/management') _.set(source, field.name, value); }); - savedObjectsClient.update(service.type, $routeParams.id, source) + const { references, ...attributes } = source; + + savedObjectsClient.update(service.type, $routeParams.id, attributes, { references }) .then(function () { return redirectHandler('updated'); }) diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/relationships/__jest__/relationships.test.js b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/relationships/__jest__/relationships.test.js index 9eb269ea46440..fabbfce0395e3 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/relationships/__jest__/relationships.test.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/relationships/__jest__/relationships.test.js @@ -83,12 +83,12 @@ describe('Relationships', () => { it('should render searches normally', async () => { const props = { getRelationships: jest.fn().mockImplementation(() => ({ - indexPatterns: [ + 'index-pattern': [ { id: '1', } ], - visualizations: [ + visualization: [ { id: '2', } @@ -123,7 +123,7 @@ describe('Relationships', () => { it('should render visualizations normally', async () => { const props = { getRelationships: jest.fn().mockImplementation(() => ({ - dashboards: [ + dashboard: [ { id: '1', }, @@ -161,7 +161,7 @@ describe('Relationships', () => { it('should render dashboards normally', async () => { const props = { getRelationships: jest.fn().mockImplementation(() => ({ - visualizations: [ + visualization: [ { id: '1', }, diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/relationships/relationships.js b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/relationships/relationships.js index c73f77d2687c9..b7491583edf61 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/relationships/relationships.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/relationships/relationships.js @@ -148,7 +148,7 @@ class RelationshipsUI extends Component { />); break; case 'search': - if (type === 'visualizations') { + if (type === 'visualization') { calloutText = (); + if (type === 'index-pattern') { + calloutColor = 'success'; + calloutTitle = (); + calloutText = (); + } else if (type === 'search') { + calloutColor = 'success'; + calloutTitle = (); + calloutText = (); + } else { + calloutText = (); + } break; case 'index-pattern': - if (type === 'visualizations') { + if (type === 'visualization') { calloutText = (); - } else if (type === 'searches') { + } else if (type === 'search') { calloutText = ( - orange + Orange , } } @@ -2576,11 +2576,11 @@ exports[`Field for select setting should render as read only with help text if o options={ Array [ Object { - "text": "apple", + "text": "Apple", "value": "apple", }, Object { - "text": "orange", + "text": "Orange", "value": "orange", }, Object { @@ -2677,11 +2677,11 @@ exports[`Field for select setting should render custom setting icon if it is cus options={ Array [ Object { - "text": "apple", + "text": "Apple", "value": "apple", }, Object { - "text": "orange", + "text": "Orange", "value": "orange", }, Object { @@ -2767,11 +2767,11 @@ exports[`Field for select setting should render default value if there is no use options={ Array [ Object { - "text": "apple", + "text": "Apple", "value": "apple", }, Object { - "text": "orange", + "text": "Orange", "value": "orange", }, Object { @@ -2833,7 +2833,7 @@ exports[`Field for select setting should render user value if there is user valu values={ Object { "value": - orange + Orange , } } @@ -2899,11 +2899,11 @@ exports[`Field for select setting should render user value if there is user valu options={ Array [ Object { - "text": "apple", + "text": "Apple", "value": "apple", }, Object { - "text": "orange", + "text": "Orange", "value": "orange", }, Object { diff --git a/src/legacy/core_plugins/kibana/public/management/sections/settings/components/field/field.js b/src/legacy/core_plugins/kibana/public/management/sections/settings/components/field/field.js index 6724fe59c5146..2d87ca4f69163 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/settings/components/field/field.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/settings/components/field/field.js @@ -103,13 +103,15 @@ class FieldUI extends PureComponent { } } - getDisplayedDefaultValue(type, defVal) { + getDisplayedDefaultValue(type, defVal, optionLabels = {}) { if (defVal === undefined || defVal === null || defVal === '') { return 'null'; } switch (type) { case 'array': return defVal.join(', '); + case 'select': + return optionLabels.hasOwnProperty(defVal) ? optionLabels[defVal] : String(defVal); default: return String(defVal); } @@ -348,7 +350,7 @@ class FieldUI extends PureComponent { renderField(setting) { const { loading, changeImage, unsavedValue } = this.state; - const { name, value, type, options, isOverridden, ariaName } = setting; + const { name, value, type, options, optionLabels = {}, isOverridden, ariaName } = setting; switch (type) { case 'boolean': @@ -426,8 +428,11 @@ class FieldUI extends PureComponent { { - return { text, value: text }; + options={options.map((option) => { + return { + text: optionLabels.hasOwnProperty(option) ? optionLabels[option] : option, + value: option + }; })} onChange={this.onFieldChange} isLoading={loading} @@ -542,7 +547,7 @@ class FieldUI extends PureComponent { } renderDefaultValue(setting) { - const { type, defVal } = setting; + const { type, defVal, optionLabels } = setting; if (isDefaultValue(setting)) { return; } @@ -574,7 +579,7 @@ class FieldUI extends PureComponent { id="kbn.management.settings.field.defaultValueText" defaultMessage="Default: {value}" values={{ - value: ({this.getDisplayedDefaultValue(type, defVal)}), + value: ({this.getDisplayedDefaultValue(type, defVal, optionLabels)}), }} /> diff --git a/src/legacy/core_plugins/kibana/public/management/sections/settings/components/field/field.test.js b/src/legacy/core_plugins/kibana/public/management/sections/settings/components/field/field.test.js index 91267758310e3..12994260afe96 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/settings/components/field/field.test.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/settings/components/field/field.test.js @@ -122,6 +122,11 @@ const settings = { isCustom: false, isOverridden: false, options: ['apple', 'orange', 'banana'], + optionLabels: { + apple: 'Apple', + orange: 'Orange', + // Deliberately left out `banana` to test if it also works with missing labels + } }, string: { name: 'string:test:setting', @@ -213,6 +218,40 @@ describe('Field', () => { }); }); + if(type === 'select') { + it('should use options for rendering values', () => { + const component = mountWithIntl( + + ); + const select = findTestSubject(component, `advancedSetting-editField-${setting.name}`); + const labels = select.find('option').map(option => option.prop('value')); + expect(labels).toEqual(['apple', 'orange', 'banana']); + }); + + it('should use optionLabels for rendering labels', () => { + const component = mountWithIntl( + + ); + const select = findTestSubject(component, `advancedSetting-editField-${setting.name}`); + const labels = select.find('option').map(option => option.text()); + expect(labels).toEqual(['Apple', 'Orange', 'banana']); + }); + } + if(type === 'image') { describe(`for changing ${type} setting`, () => { const component = mountWithIntl( diff --git a/src/legacy/core_plugins/kibana/public/management/sections/settings/lib/to_editable_config.js b/src/legacy/core_plugins/kibana/public/management/sections/settings/lib/to_editable_config.js index 0dd925d8c950a..e0f959e3c0cbf 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/settings/lib/to_editable_config.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/settings/lib/to_editable_config.js @@ -44,6 +44,7 @@ export function toEditableConfig({ def, name, value, isCustom, isOverridden }) { type: getValType(def, value), description: def.description, options: def.options, + optionLabels: def.optionLabels, }; return conf; diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js index 2ea7d44afed9d..da6471d005c78 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js +++ b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js @@ -311,7 +311,9 @@ function VisEditor( $appStatus.dirty = status.dirty || !savedVis.id; }); - $scope.$watch('state.query', $scope.updateQueryAndFetch); + $scope.$watch('state.query', (query) => { + $scope.updateQueryAndFetch({ query }); + }); $state.replace(); @@ -385,7 +387,7 @@ function VisEditor( } } - $scope.updateQueryAndFetch = function (query) { + $scope.updateQueryAndFetch = function ({ query }) { $state.query = migrateLegacyQuery(query); $scope.fetch(); }; diff --git a/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/_saved_vis.js b/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/_saved_vis.js index 0e7dc49b938b5..8bd883bb96c3c 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/_saved_vis.js +++ b/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/_saved_vis.js @@ -31,6 +31,10 @@ import { updateOldState } from 'ui/vis/vis_update_state'; import { VisualizeConstants } from '../visualize_constants'; import { createLegacyClass } from 'ui/utils/legacy_class'; import { SavedObjectProvider } from 'ui/courier'; +import { + extractReferences, + injectReferences, +} from './saved_visualization_references'; uiModules .get('app/visualize') @@ -47,6 +51,8 @@ uiModules type: SavedVis.type, mapping: SavedVis.mapping, searchSource: SavedVis.searchSource, + extractReferences: extractReferences, + injectReferences: injectReferences, id: opts.id, indexPattern: opts.indexPattern, diff --git a/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualization_references.js b/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualization_references.js new file mode 100644 index 0000000000000..953c32e58e1b7 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualization_references.js @@ -0,0 +1,51 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export function extractReferences({ attributes, references = [] }) { + if (!attributes.savedSearchId) { + return { attributes, references }; + } + return { + references: [ + ...references, + { + type: 'search', + name: 'search_0', + id: attributes.savedSearchId, + }, + ], + attributes: { + ...attributes, + savedSearchId: undefined, + savedSearchRefName: 'search_0', + }, + }; +} + +export function injectReferences(savedObject, references) { + if (!savedObject.savedSearchRefName) { + return; + } + const reference = references.find(reference => reference.name === savedObject.savedSearchRefName); + if (!reference) { + throw new Error(`Could not find reference "${savedObject.savedSearchRefName}"`); + } + savedObject.savedSearchId = reference.id; + delete savedObject.savedSearchRefName; +} diff --git a/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualization_references.test.js b/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualization_references.test.js new file mode 100644 index 0000000000000..be9375dc33e56 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualization_references.test.js @@ -0,0 +1,117 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { extractReferences, injectReferences } from './saved_visualization_references'; + +describe('extractReferences', () => { + test('extracts nothing if savedSearchId is empty', () => { + const doc = { + id: '1', + attributes: { + foo: true, + }, + }; + const updatedDoc = extractReferences(doc); + expect(updatedDoc).toMatchInlineSnapshot(` +Object { + "attributes": Object { + "foo": true, + }, + "references": Array [], +} +`); + }); + + test('extracts references from savedSearchId', () => { + const doc = { + id: '1', + attributes: { + foo: true, + savedSearchId: '123', + }, + }; + const updatedDoc = extractReferences(doc); + expect(updatedDoc).toMatchInlineSnapshot(` +Object { + "attributes": Object { + "foo": true, + "savedSearchId": undefined, + "savedSearchRefName": "search_0", + }, + "references": Array [ + Object { + "id": "123", + "name": "search_0", + "type": "search", + }, + ], +} +`); + }); +}); + +describe('injectReferences', () => { + test('injects nothing when savedSearchRefName is null', () => { + const context = { + id: '1', + foo: true, + }; + injectReferences(context, []); + expect(context).toMatchInlineSnapshot(` +Object { + "foo": true, + "id": "1", +} +`); + }); + + test('injects references into context', () => { + const context = { + id: '1', + foo: true, + savedSearchRefName: 'search_0', + }; + const references = [ + { + name: 'search_0', + type: 'search', + id: '123', + }, + ]; + injectReferences(context, references); + expect(context).toMatchInlineSnapshot(` +Object { + "foo": true, + "id": "1", + "savedSearchId": "123", +} +`); + }); + + test(`fails when it can't find the reference in the array`, () => { + const context = { + id: '1', + foo: true, + savedSearchRefName: 'search_0', + }; + expect(() => injectReferences(context, [])).toThrowErrorMatchingInlineSnapshot( + `"Could not find reference \\"search_0\\""` + ); + }); +}); diff --git a/src/legacy/core_plugins/kibana/server/lib/__tests__/relationships.js b/src/legacy/core_plugins/kibana/server/lib/__tests__/relationships.js index 1c5473fe5e311..42845b4cf0eb1 100644 --- a/src/legacy/core_plugins/kibana/server/lib/__tests__/relationships.js +++ b/src/legacy/core_plugins/kibana/server/lib/__tests__/relationships.js @@ -27,28 +27,44 @@ describe('findRelationships', () => { const size = 10; const savedObjectsClient = { - _index: '.kibana', get: () => ({ attributes: { - panelsJSON: JSON.stringify([{ id: '1' }, { id: '2' }, { id: '3' }]), + panelsJSON: JSON.stringify([{ panelRefName: 'panel_0' }, { panelRefName: 'panel_1' }, { panelRefName: 'panel_2' }]), }, + references: [{ + name: 'panel_0', + type: 'visualization', + id: '1', + }, { + name: 'panel_1', + type: 'visualization', + id: '2', + }, { + name: 'panel_2', + type: 'visualization', + id: '3', + }], }), - bulkGet: () => ({ + bulkGet: () => ({ saved_objects: [] }), + find: () => ({ saved_objects: [ { id: '1', + type: 'visualization', attributes: { title: 'Foo', }, }, { id: '2', + type: 'visualization', attributes: { title: 'Bar', }, }, { id: '3', + type: 'visualization', attributes: { title: 'FooBar', }, @@ -59,11 +75,14 @@ describe('findRelationships', () => { const result = await findRelationships( type, id, - size, - savedObjectsClient + { + size, + savedObjectsClient, + savedObjectTypes: ['dashboard', 'visualization', 'search', 'index-pattern'], + }, ); expect(result).to.eql({ - visualizations: [ + visualization: [ { id: '1', title: 'Foo' }, { id: '2', title: 'Bar' }, { id: '3', title: 'FooBar' }, @@ -77,11 +96,36 @@ describe('findRelationships', () => { const size = 10; const savedObjectsClient = { - get: () => {}, + get: () => ({ + attributes: { + kibanaSavedObjectMeta: { + searchSourceJSON: JSON.stringify({ + indexRefName: 'kibanaSavedObjectMeta.searchSourceJSON.index', + }), + }, + }, + references: [{ + name: 'kibanaSavedObjectMeta.searchSourceJSON.index', + type: 'index-pattern', + id: '1', + }], + }), + bulkGet: () => ({ + saved_objects: [ + { + id: '1', + type: 'index-pattern', + attributes: { + title: 'My Index Pattern', + }, + }, + ], + }), find: () => ({ saved_objects: [ { id: '1', + type: 'dashboard', attributes: { title: 'My Dashboard', panelsJSON: JSON.stringify([ @@ -98,6 +142,7 @@ describe('findRelationships', () => { }, { id: '2', + type: 'dashboard', attributes: { title: 'Your Dashboard', panelsJSON: JSON.stringify([ @@ -119,11 +164,17 @@ describe('findRelationships', () => { const result = await findRelationships( type, id, - size, - savedObjectsClient + { + size, + savedObjectsClient, + savedObjectTypes: ['dashboard', 'visualization', 'search', 'index-pattern'], + }, ); expect(result).to.eql({ - dashboards: [ + 'index-pattern': [ + { id: '1', title: 'My Index Pattern' }, + ], + dashboard: [ { id: '1', title: 'My Dashboard' }, { id: '2', title: 'Your Dashboard' }, ], @@ -136,43 +187,52 @@ describe('findRelationships', () => { const size = 10; const savedObjectsClient = { - get: type => { - if (type === 'search') { - return { + get: () => ({ + id: '1', + type: 'search', + attributes: { + kibanaSavedObjectMeta: { + searchSourceJSON: JSON.stringify({ + indexRefName: 'kibanaSavedObjectMeta.searchSourceJSON.index', + }), + }, + }, + references: [{ + name: 'kibanaSavedObjectMeta.searchSourceJSON.index', + type: 'index-pattern', + id: '1', + }], + }), + bulkGet: () => ({ + saved_objects: [ + { id: '1', + type: 'index-pattern', attributes: { - kibanaSavedObjectMeta: { - searchSourceJSON: JSON.stringify({ - index: 'index-pattern:1', - }), - }, + title: 'My Index Pattern', }, - }; - } - - return { - id: 'index-pattern:1', - attributes: { - title: 'My Index Pattern', }, - }; - }, + ], + }), find: () => ({ saved_objects: [ { id: '1', + type: 'visualization', attributes: { title: 'Foo', }, }, { id: '2', + type: 'visualization', attributes: { title: 'Bar', }, }, { id: '3', + type: 'visualization', attributes: { title: 'FooBar', }, @@ -184,16 +244,19 @@ describe('findRelationships', () => { const result = await findRelationships( type, id, - size, - savedObjectsClient + { + size, + savedObjectsClient, + savedObjectTypes: ['dashboard', 'visualization', 'search', 'index-pattern'], + }, ); expect(result).to.eql({ - visualizations: [ + visualization: [ { id: '1', title: 'Foo' }, { id: '2', title: 'Bar' }, { id: '3', title: 'FooBar' }, ], - indexPatterns: [{ id: 'index-pattern:1', title: 'My Index Pattern' }], + 'index-pattern': [{ id: '1', title: 'My Index Pattern' }], }); }); @@ -203,106 +266,103 @@ describe('findRelationships', () => { const size = 10; const savedObjectsClient = { - get: () => {}, - find: options => { - if (options.type === 'visualization') { - return { - saved_objects: [ - { - id: '1', - found: true, - attributes: { - title: 'Foo', - kibanaSavedObjectMeta: { - searchSourceJSON: JSON.stringify({ - index: 'foo', - }), - }, - }, - }, - { - id: '2', - found: true, - attributes: { - title: 'Bar', - kibanaSavedObjectMeta: { - searchSourceJSON: JSON.stringify({ - index: 'foo', - }), - }, - }, - }, - { - id: '3', - found: true, - attributes: { - title: 'FooBar', - kibanaSavedObjectMeta: { - searchSourceJSON: JSON.stringify({ - index: 'foo2', - }), - }, - }, - }, - ] - }; - } - - return { - saved_objects: [ - { - id: '1', - attributes: { - title: 'Foo', - kibanaSavedObjectMeta: { - searchSourceJSON: JSON.stringify({ - index: 'foo', - }), - }, + get: () => ({ + id: '1', + type: 'index-pattern', + attributes: { + title: 'My Index Pattern' + }, + }), + find: () => ({ + saved_objects: [ + { + id: '1', + type: 'visualization', + attributes: { + title: 'Foo', + kibanaSavedObjectMeta: { + searchSourceJSON: JSON.stringify({ + index: 'foo', + }), }, }, - { - id: '2', - attributes: { - title: 'Bar', - kibanaSavedObjectMeta: { - searchSourceJSON: JSON.stringify({ - index: 'foo', - }), - }, + }, + { + id: '2', + type: 'visualization', + attributes: { + title: 'Bar', + kibanaSavedObjectMeta: { + searchSourceJSON: JSON.stringify({ + index: 'foo', + }), }, }, - { - id: '3', - attributes: { - title: 'FooBar', - kibanaSavedObjectMeta: { - searchSourceJSON: JSON.stringify({ - index: 'foo2', - }), - }, + }, + { + id: '3', + type: 'visualization', + attributes: { + title: 'FooBar', + kibanaSavedObjectMeta: { + searchSourceJSON: JSON.stringify({ + index: 'foo2', + }), }, }, - ] - }; - } + }, + { + id: '1', + type: 'search', + attributes: { + title: 'My Saved Search', + }, + }, + ], + }), }; const result = await findRelationships( type, id, - size, - savedObjectsClient + { + size, + savedObjectsClient, + savedObjectTypes: ['dashboard', 'visualization', 'search', 'index-pattern'], + }, ); expect(result).to.eql({ - visualizations: [{ id: '1', title: 'Foo' }, { id: '2', title: 'Bar' }], - searches: [{ id: '1', title: 'Foo' }, { id: '2', title: 'Bar' }], + visualization: [{ id: '1', title: 'Foo' }, { id: '2', title: 'Bar' }, { id: '3', title: 'FooBar' }], + search: [{ id: '1', title: 'My Saved Search' }], }); }); - it('should return an empty object for invalid types', async () => { + it('should return an empty object for non related objects', async () => { const type = 'invalid'; - const result = await findRelationships(type); + const id = 'foo'; + const size = 10; + + const savedObjectsClient = { + get: () => ({ + id: '1', + type: 'index-pattern', + attributes: { + title: 'My Index Pattern', + }, + references: [], + }), + find: () => ({ saved_objects: [] }), + }; + + const result = await findRelationships( + type, + id, + { + size, + savedObjectsClient, + savedObjectTypes: ['dashboard', 'visualization', 'search', 'index-pattern'], + }, + ); expect(result).to.eql({}); }); }); diff --git a/src/legacy/core_plugins/kibana/server/lib/export/__tests__/collect_dashboards.js b/src/legacy/core_plugins/kibana/server/lib/export/__tests__/collect_dashboards.js deleted file mode 100644 index fbbe0cc6e3701..0000000000000 --- a/src/legacy/core_plugins/kibana/server/lib/export/__tests__/collect_dashboards.js +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import sinon from 'sinon'; -import * as deps from '../collect_panels'; -import { collectDashboards } from '../collect_dashboards'; -import { expect } from 'chai'; - -describe('collectDashboards(req, ids)', () => { - - let collectPanelsStub; - const savedObjectsClient = { bulkGet: sinon.stub() }; - - const ids = ['dashboard-01', 'dashboard-02']; - - beforeEach(() => { - collectPanelsStub = sinon.stub(deps, 'collectPanels'); - collectPanelsStub.onFirstCall().returns(Promise.resolve([ - { id: 'dashboard-01' }, - { id: 'panel-01' }, - { id: 'index-*' } - ])); - collectPanelsStub.onSecondCall().returns(Promise.resolve([ - { id: 'dashboard-02' }, - { id: 'panel-01' }, - { id: 'index-*' } - ])); - - savedObjectsClient.bulkGet.returns(Promise.resolve({ - saved_objects: [ - { id: 'dashboard-01' }, { id: 'dashboard-02' } - ] - })); - }); - - afterEach(() => { - collectPanelsStub.restore(); - savedObjectsClient.bulkGet.resetHistory(); - }); - - it('should request all dashboards', async () => { - await collectDashboards(savedObjectsClient, ids); - - expect(savedObjectsClient.bulkGet.calledOnce).to.equal(true); - - const args = savedObjectsClient.bulkGet.getCall(0).args; - expect(args[0]).to.eql([{ - id: 'dashboard-01', - type: 'dashboard' - }, { - id: 'dashboard-02', - type: 'dashboard' - }]); - }); - - it('should call collectPanels with dashboard docs', async () => { - await collectDashboards(savedObjectsClient, ids); - - expect(collectPanelsStub.calledTwice).to.equal(true); - expect(collectPanelsStub.args[0][1]).to.eql({ id: 'dashboard-01' }); - expect(collectPanelsStub.args[1][1]).to.eql({ id: 'dashboard-02' }); - }); - - it('should return an unique list of objects', async () => { - const results = await collectDashboards(savedObjectsClient, ids); - expect(results).to.eql([ - { id: 'dashboard-01' }, - { id: 'panel-01' }, - { id: 'index-*' }, - { id: 'dashboard-02' }, - ]); - }); -}); diff --git a/src/legacy/core_plugins/kibana/server/lib/export/__tests__/collect_index_patterns.js b/src/legacy/core_plugins/kibana/server/lib/export/__tests__/collect_index_patterns.js deleted file mode 100644 index edbb8ecbf10b3..0000000000000 --- a/src/legacy/core_plugins/kibana/server/lib/export/__tests__/collect_index_patterns.js +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import sinon from 'sinon'; -import { collectIndexPatterns } from '../collect_index_patterns'; -import { expect } from 'chai'; - -describe('collectIndexPatterns(req, panels)', () => { - const panels = [ - { - attributes: { - kibanaSavedObjectMeta: { - searchSourceJSON: JSON.stringify({ index: 'index-*' }) - } - } - }, { - attributes: { - kibanaSavedObjectMeta: { - searchSourceJSON: JSON.stringify({ index: 'logstash-*' }) - } - } - }, { - attributes: { - kibanaSavedObjectMeta: { - searchSourceJSON: JSON.stringify({ index: 'logstash-*' }) - } - } - }, { - attributes: { - savedSearchId: 1, - kibanaSavedObjectMeta: { - searchSourceJSON: JSON.stringify({ index: 'bad-*' }) - } - } - } - ]; - - const savedObjectsClient = { bulkGet: sinon.stub() }; - - beforeEach(() => { - savedObjectsClient.bulkGet.returns(Promise.resolve({ - saved_objects: [ - { id: 'index-*' }, { id: 'logstash-*' } - ] - })); - }); - - afterEach(() => { - savedObjectsClient.bulkGet.resetHistory(); - }); - - it('should request all index patterns', async () => { - await collectIndexPatterns(savedObjectsClient, panels); - - expect(savedObjectsClient.bulkGet.calledOnce).to.equal(true); - expect(savedObjectsClient.bulkGet.getCall(0).args[0]).to.eql([{ - id: 'index-*', - type: 'index-pattern' - }, { - id: 'logstash-*', - type: 'index-pattern' - }]); - }); - - it('should return the index pattern docs', async () => { - const results = await collectIndexPatterns(savedObjectsClient, panels); - - expect(results).to.eql([ - { id: 'index-*' }, - { id: 'logstash-*' } - ]); - }); - - it('should return an empty array if nothing is requested', async () => { - const input = [ - { - attributes: { - savedSearchId: 1, - kibanaSavedObjectMeta: { - searchSourceJSON: JSON.stringify({ index: 'bad-*' }) - } - } - } - ]; - - const results = await collectIndexPatterns(savedObjectsClient, input); - expect(results).to.eql([]); - expect(savedObjectsClient.bulkGet.calledOnce).to.eql(false); - }); -}); diff --git a/src/legacy/core_plugins/kibana/server/lib/export/__tests__/collect_panels.js b/src/legacy/core_plugins/kibana/server/lib/export/__tests__/collect_panels.js deleted file mode 100644 index f15d6a088f997..0000000000000 --- a/src/legacy/core_plugins/kibana/server/lib/export/__tests__/collect_panels.js +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import sinon from 'sinon'; -import * as collectIndexPatternsDep from '../collect_index_patterns'; -import * as collectSearchSourcesDep from '../collect_search_sources'; -import { collectPanels } from '../collect_panels'; -import { expect } from 'chai'; - -describe('collectPanels(req, dashboard)', () => { - let collectSearchSourcesStub; - let collectIndexPatternsStub; - let dashboard; - - const savedObjectsClient = { bulkGet: sinon.stub() }; - - beforeEach(() => { - dashboard = { - attributes: { - panelsJSON: JSON.stringify([ - { id: 'panel-01', type: 'search' }, - { id: 'panel-02', type: 'visualization' } - ]) - } - }; - - savedObjectsClient.bulkGet.returns(Promise.resolve({ - saved_objects: [ - { id: 'panel-01' }, { id: 'panel-02' } - ] - })); - - collectIndexPatternsStub = sinon.stub(collectIndexPatternsDep, 'collectIndexPatterns'); - collectIndexPatternsStub.returns([{ id: 'logstash-*' }]); - collectSearchSourcesStub = sinon.stub(collectSearchSourcesDep, 'collectSearchSources'); - collectSearchSourcesStub.returns([ { id: 'search-01' }]); - }); - - afterEach(() => { - collectSearchSourcesStub.restore(); - collectIndexPatternsStub.restore(); - savedObjectsClient.bulkGet.resetHistory(); - }); - - it('should request each panel in the panelJSON', async () => { - await collectPanels(savedObjectsClient, dashboard); - - expect(savedObjectsClient.bulkGet.calledOnce).to.equal(true); - expect(savedObjectsClient.bulkGet.getCall(0).args[0]).to.eql([{ - id: 'panel-01', - type: 'search' - }, { - id: 'panel-02', - type: 'visualization' - }]); - }); - - it('should call collectSearchSources()', async () => { - await collectPanels(savedObjectsClient, dashboard); - expect(collectSearchSourcesStub.calledOnce).to.equal(true); - expect(collectSearchSourcesStub.args[0][1]).to.eql([ - { id: 'panel-01' }, - { id: 'panel-02' } - ]); - }); - - it('should call collectIndexPatterns()', async () => { - await collectPanels(savedObjectsClient, dashboard); - - expect(collectIndexPatternsStub.calledOnce).to.equal(true); - expect(collectIndexPatternsStub.args[0][1]).to.eql([ - { id: 'panel-01' }, - { id: 'panel-02' } - ]); - }); - - it('should return panels, index patterns, search sources, and dashboard', async () => { - const results = await collectPanels(savedObjectsClient, dashboard); - - expect(results).to.eql([ - { id: 'panel-01' }, - { id: 'panel-02' }, - { id: 'logstash-*' }, - { id: 'search-01' }, - dashboard - ]); - }); - -}); diff --git a/src/legacy/core_plugins/kibana/server/lib/export/__tests__/collect_search_sources.js b/src/legacy/core_plugins/kibana/server/lib/export/__tests__/collect_search_sources.js deleted file mode 100644 index 4f9de8a971b7c..0000000000000 --- a/src/legacy/core_plugins/kibana/server/lib/export/__tests__/collect_search_sources.js +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import sinon from 'sinon'; -import * as deps from '../collect_index_patterns'; -import { collectSearchSources } from '../collect_search_sources'; -import { expect } from 'chai'; -describe('collectSearchSources(req, panels)', () => { - const savedObjectsClient = { bulkGet: sinon.stub() }; - - let panels; - let collectIndexPatternsStub; - - beforeEach(() => { - panels = [ - { attributes: { savedSearchId: 1 } }, - { attributes: { savedSearchId: 2 } } - ]; - - collectIndexPatternsStub = sinon.stub(deps, 'collectIndexPatterns'); - collectIndexPatternsStub.returns(Promise.resolve([{ id: 'logstash-*' }])); - - savedObjectsClient.bulkGet.returns(Promise.resolve({ - saved_objects: [ - { id: 1 }, { id: 2 } - ] - })); - }); - - afterEach(() => { - collectIndexPatternsStub.restore(); - savedObjectsClient.bulkGet.resetHistory(); - }); - - it('should request all search sources', async () => { - await collectSearchSources(savedObjectsClient, panels); - - expect(savedObjectsClient.bulkGet.calledOnce).to.equal(true); - expect(savedObjectsClient.bulkGet.getCall(0).args[0]).to.eql([ - { type: 'search', id: 1 }, { type: 'search', id: 2 } - ]); - }); - - it('should return the search source and index patterns', async () => { - const results = await collectSearchSources(savedObjectsClient, panels); - - expect(results).to.eql([ - { id: 1 }, - { id: 2 }, - { id: 'logstash-*' } - ]); - }); - - it('should return an empty array if nothing is requested', async () => { - const input = [ - { - attributes: { - kibanaSavedObjectMeta: { - searchSourceJSON: JSON.stringify({ index: 'bad-*' }) - } - } - } - ]; - - const results = await collectSearchSources(savedObjectsClient, input); - expect(results).to.eql([]); - expect(savedObjectsClient.bulkGet.calledOnce).to.eql(false); - }); -}); diff --git a/src/legacy/core_plugins/kibana/server/lib/export/__tests__/export_dashboards.js b/src/legacy/core_plugins/kibana/server/lib/export/__tests__/export_dashboards.js deleted file mode 100644 index 1fb93ec4d95c5..0000000000000 --- a/src/legacy/core_plugins/kibana/server/lib/export/__tests__/export_dashboards.js +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import * as deps from '../collect_dashboards'; -import { exportDashboards } from '../export_dashboards'; -import sinon from 'sinon'; -import { expect } from 'chai'; - -describe('exportDashboards(req)', () => { - - let req; - let collectDashboardsStub; - - beforeEach(() => { - req = { - query: { dashboard: 'dashboard-01' }, - server: { - config: () => ({ get: () => '6.0.0' }), - plugins: { - elasticsearch: { - getCluster: () => ({ callWithRequest: sinon.stub() }) - } - }, - }, - getSavedObjectsClient() { - return null; - } - }; - - collectDashboardsStub = sinon.stub(deps, 'collectDashboards'); - collectDashboardsStub.returns(Promise.resolve([ - { id: 'dashboard-01' }, - { id: 'logstash-*' }, - { id: 'panel-01' } - ])); - }); - - afterEach(() => { - collectDashboardsStub.restore(); - }); - - it('should return a response object with version', () => { - return exportDashboards(req).then((resp) => { - expect(resp).to.have.property('version', '6.0.0'); - }); - }); - - it('should return a response object with objects', () => { - return exportDashboards(req).then((resp) => { - expect(resp).to.have.property('objects'); - expect(resp.objects).to.eql([ - { id: 'dashboard-01' }, - { id: 'logstash-*' }, - { id: 'panel-01' } - ]); - }); - }); -}); diff --git a/src/legacy/core_plugins/kibana/server/lib/export/collect_references_deep.test.ts b/src/legacy/core_plugins/kibana/server/lib/export/collect_references_deep.test.ts new file mode 100644 index 0000000000000..f5b9b299fd766 --- /dev/null +++ b/src/legacy/core_plugins/kibana/server/lib/export/collect_references_deep.test.ts @@ -0,0 +1,196 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { SavedObject } from '../../../../../../server/saved_objects/service/saved_objects_client'; +import { collectReferencesDeep } from './collect_references_deep'; + +const data = [ + { + id: '1', + type: 'dashboard', + attributes: { + panelsJSON: JSON.stringify([{ panelRefName: 'panel_0' }, { panelRefName: 'panel_1' }]), + }, + references: [ + { + name: 'panel_0', + type: 'visualization', + id: '2', + }, + { + name: 'panel_1', + type: 'visualization', + id: '3', + }, + ], + }, + { + id: '2', + type: 'visualization', + attributes: { + kibanaSavedObjectMeta: { + searchSourceJSON: JSON.stringify({ + indexRefName: 'kibanaSavedObjectMeta.searchSourceJSON.index', + }), + }, + }, + references: [ + { + name: 'kibanaSavedObjectMeta.searchSourceJSON.index', + type: 'index-pattern', + id: '4', + }, + ], + }, + { + id: '3', + type: 'visualization', + attributes: { + savedSearchRefName: 'search_0', + }, + references: [ + { + name: 'search_0', + type: 'search', + id: '5', + }, + ], + }, + { + id: '4', + type: 'index-pattern', + attributes: { + title: 'pattern*', + }, + }, + { + id: '5', + type: 'search', + attributes: { + kibanaSavedObjectMeta: { + searchSourceJSON: JSON.stringify({ + indexRefName: 'kibanaSavedObjectMeta.searchSourceJSON.index', + }), + }, + }, + references: [ + { + name: 'kibanaSavedObjectMeta.searchSourceJSON.index', + type: 'index-pattern', + id: '4', + }, + ], + }, +]; + +test('collects dashboard and all dependencies', async () => { + const savedObjectClient = { + errors: {} as any, + create: jest.fn(), + bulkCreate: jest.fn(), + delete: jest.fn(), + find: jest.fn(), + get: jest.fn(), + update: jest.fn(), + bulkGet: jest.fn(getObjects => { + return { + saved_objects: getObjects.map((obj: SavedObject) => + data.find(row => row.id === obj.id && row.type === obj.type) + ), + }; + }), + }; + const objects = await collectReferencesDeep(savedObjectClient, [{ type: 'dashboard', id: '1' }]); + expect(objects).toMatchInlineSnapshot(` +Array [ + Object { + "attributes": Object { + "panelsJSON": "[{\\"panelRefName\\":\\"panel_0\\"},{\\"panelRefName\\":\\"panel_1\\"}]", + }, + "id": "1", + "references": Array [ + Object { + "id": "2", + "name": "panel_0", + "type": "visualization", + }, + Object { + "id": "3", + "name": "panel_1", + "type": "visualization", + }, + ], + "type": "dashboard", + }, + Object { + "attributes": Object { + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": "{\\"indexRefName\\":\\"kibanaSavedObjectMeta.searchSourceJSON.index\\"}", + }, + }, + "id": "2", + "references": Array [ + Object { + "id": "4", + "name": "kibanaSavedObjectMeta.searchSourceJSON.index", + "type": "index-pattern", + }, + ], + "type": "visualization", + }, + Object { + "attributes": Object { + "savedSearchRefName": "search_0", + }, + "id": "3", + "references": Array [ + Object { + "id": "5", + "name": "search_0", + "type": "search", + }, + ], + "type": "visualization", + }, + Object { + "attributes": Object { + "title": "pattern*", + }, + "id": "4", + "type": "index-pattern", + }, + Object { + "attributes": Object { + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": "{\\"indexRefName\\":\\"kibanaSavedObjectMeta.searchSourceJSON.index\\"}", + }, + }, + "id": "5", + "references": Array [ + Object { + "id": "4", + "name": "kibanaSavedObjectMeta.searchSourceJSON.index", + "type": "index-pattern", + }, + ], + "type": "search", + }, +] +`); +}); diff --git a/src/legacy/core_plugins/kibana/server/lib/export/collect_references_deep.ts b/src/legacy/core_plugins/kibana/server/lib/export/collect_references_deep.ts new file mode 100644 index 0000000000000..f62fc3c474c08 --- /dev/null +++ b/src/legacy/core_plugins/kibana/server/lib/export/collect_references_deep.ts @@ -0,0 +1,55 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { + SavedObject, + SavedObjectsClient, +} from '../../../../../../server/saved_objects/service/saved_objects_client'; + +const MAX_BULK_GET_SIZE = 10000; + +interface ObjectsToCollect { + id: string; + type: string; +} + +export async function collectReferencesDeep( + savedObjectClient: SavedObjectsClient, + objects: ObjectsToCollect[] +) { + let result: SavedObject[] = []; + const queue = [...objects]; + while (queue.length !== 0) { + const itemsToGet = queue.splice(0, MAX_BULK_GET_SIZE); + const { saved_objects: savedObjects } = await savedObjectClient.bulkGet(itemsToGet); + result = result.concat(savedObjects); + for (const { references = [] } of savedObjects) { + for (const reference of references) { + const isDuplicate = queue + .concat(result) + .some(obj => obj.type === reference.type && obj.id === reference.id); + if (isDuplicate) { + continue; + } + queue.push({ type: reference.type, id: reference.id }); + } + } + } + return result; +} diff --git a/src/legacy/core_plugins/kibana/server/lib/export/export_dashboards.js b/src/legacy/core_plugins/kibana/server/lib/export/export_dashboards.js index ed61d2c83366a..07b1bafd61c91 100644 --- a/src/legacy/core_plugins/kibana/server/lib/export/export_dashboards.js +++ b/src/legacy/core_plugins/kibana/server/lib/export/export_dashboards.js @@ -18,7 +18,7 @@ */ import _ from 'lodash'; -import { collectDashboards } from './collect_dashboards'; +import { collectReferencesDeep } from './collect_references_deep'; export async function exportDashboards(req) { @@ -26,8 +26,9 @@ export async function exportDashboards(req) { const config = req.server.config(); const savedObjectsClient = req.getSavedObjectsClient(); + const objectsToExport = ids.map(id => ({ id, type: 'dashboard' })); - const objects = await collectDashboards(savedObjectsClient, ids); + const objects = await collectReferencesDeep(savedObjectsClient, objectsToExport); return { version: config.get('pkg.version'), objects diff --git a/src/legacy/core_plugins/kibana/server/lib/management/saved_objects/relationships.js b/src/legacy/core_plugins/kibana/server/lib/management/saved_objects/relationships.js index e98bebe4c4066..afed200476343 100644 --- a/src/legacy/core_plugins/kibana/server/lib/management/saved_objects/relationships.js +++ b/src/legacy/core_plugins/kibana/server/lib/management/saved_objects/relationships.js @@ -17,166 +17,45 @@ * under the License. */ -async function findDashboardRelationships(id, size, savedObjectsClient) { - const dashboard = await savedObjectsClient.get('dashboard', id); - const visualizations = []; - - // TODO: should we handle exceptions here or at the parent level? - const panelsJSON = JSON.parse(dashboard.attributes.panelsJSON); - if (panelsJSON) { - const visualizationIds = panelsJSON.map(panel => panel.id); - const visualizationResponse = await savedObjectsClient.bulkGet( - visualizationIds.slice(0, size).map(id => ({ - id, - type: 'visualization', - })) - ); - - visualizations.push( - ...visualizationResponse.saved_objects.reduce((accum, object) => { - if (!object.error) { - accum.push({ - id: object.id, - title: object.attributes.title, - }); - } - return accum; - }, []) - ); - } - - return { visualizations }; -} - -async function findVisualizationRelationships(id, size, savedObjectsClient) { - await savedObjectsClient.get('visualization', id); - const allDashboardsResponse = await savedObjectsClient.find({ - type: 'dashboard', - fields: ['title', 'panelsJSON'], - }); - - const dashboards = []; - for (const dashboard of allDashboardsResponse.saved_objects) { - if (dashboard.error) { - continue; - } - const panelsJSON = JSON.parse(dashboard.attributes.panelsJSON); - if (panelsJSON) { - for (const panel of panelsJSON) { - if (panel.type === 'visualization' && panel.id === id) { - dashboards.push({ - id: dashboard.id, - title: dashboard.attributes.title, - }); - } - } - } - - if (dashboards.length >= size) { - break; - } - } - return { dashboards }; -} - -async function findSavedSearchRelationships(id, size, savedObjectsClient) { - const search = await savedObjectsClient.get('search', id); - - const searchSourceJSON = JSON.parse(search.attributes.kibanaSavedObjectMeta.searchSourceJSON); - - const indexPatterns = []; - try { - const indexPattern = await savedObjectsClient.get('index-pattern', searchSourceJSON.index); - indexPatterns.push({ id: indexPattern.id, title: indexPattern.attributes.title }); - } catch (err) { - // Do nothing - } - - const allVisualizationsResponse = await savedObjectsClient.find({ - type: 'visualization', - searchFields: ['savedSearchId'], - search: id, - fields: ['title'], - }); - - const visualizations = allVisualizationsResponse.saved_objects.reduce((accum, object) => { - if (!object.error) { - accum.push({ - id: object.id, - title: object.attributes.title, - }); - } - return accum; - }, []); - - return { visualizations, indexPatterns }; -} - -async function findIndexPatternRelationships(id, size, savedObjectsClient) { - await savedObjectsClient.get('index-pattern', id); - const [allVisualizationsResponse, savedSearchResponse] = await Promise.all([ +export async function findRelationships(type, id, options = {}) { + const { + size, + savedObjectsClient, + savedObjectTypes, + } = options; + + const { references = [] } = await savedObjectsClient.get(type, id); + const bulkGetOpts = references.map(ref => ({ id: ref.id, type: ref.type })); + + const [referencedObjects, referencedResponse] = await Promise.all([ + bulkGetOpts.length > 0 + ? savedObjectsClient.bulkGet(bulkGetOpts) + : Promise.resolve({ saved_objects: [] }), savedObjectsClient.find({ - type: 'visualization', - searchFields: ['kibanaSavedObjectMeta.searchSourceJSON'], - search: '*', - fields: [`title`, `kibanaSavedObjectMeta.searchSourceJSON`], - }), - savedObjectsClient.find({ - type: 'search', - searchFields: ['kibanaSavedObjectMeta.searchSourceJSON'], - search: '*', - fields: [`title`, `kibanaSavedObjectMeta.searchSourceJSON`], + hasReference: { type, id }, + perPage: size, + fields: ['title'], + type: savedObjectTypes, }), ]); - const visualizations = []; - for (const visualization of allVisualizationsResponse.saved_objects) { - if (visualization.error) { - continue; - } - const searchSourceJSON = JSON.parse(visualization.attributes.kibanaSavedObjectMeta.searchSourceJSON); - if (searchSourceJSON && searchSourceJSON.index === id) { - visualizations.push({ - id: visualization.id, - title: visualization.attributes.title, - }); - } - - if (visualizations.length >= size) { - break; - } - } - - const searches = []; - for (const search of savedSearchResponse.saved_objects) { - if (search.error) { - continue; - } - const searchSourceJSON = JSON.parse(search.attributes.kibanaSavedObjectMeta.searchSourceJSON); - if (searchSourceJSON && searchSourceJSON.index === id) { - searches.push({ - id: search.id, - title: search.attributes.title, - }); - } - - if (searches.length >= size) { - break; - } - } - return { visualizations, searches }; + const relationshipObjects = [].concat( + referencedObjects.saved_objects.map(extractCommonProperties), + referencedResponse.saved_objects.map(extractCommonProperties), + ); + + return relationshipObjects.reduce((result, relationshipObject) => { + const objectsForType = (result[relationshipObject.type] || []); + const { type, ...relationshipObjectWithoutType } = relationshipObject; + result[type] = objectsForType.concat(relationshipObjectWithoutType); + return result; + }, {}); } -export async function findRelationships(type, id, size, savedObjectsClient) { - switch (type) { - case 'dashboard': - return await findDashboardRelationships(id, size, savedObjectsClient); - case 'visualization': - return await findVisualizationRelationships(id, size, savedObjectsClient); - case 'search': - return await findSavedSearchRelationships(id, size, savedObjectsClient); - case 'index-pattern': - return await findIndexPatternRelationships(id, size, savedObjectsClient); - } - return {}; +function extractCommonProperties(savedObject) { + return { + id: savedObject.id, + type: savedObject.type, + title: savedObject.attributes.title, + }; } diff --git a/src/legacy/core_plugins/kibana/server/routes/api/management/saved_objects/relationships.js b/src/legacy/core_plugins/kibana/server/routes/api/management/saved_objects/relationships.js index d88cc05c97f8d..268f16b4f4afa 100644 --- a/src/legacy/core_plugins/kibana/server/routes/api/management/saved_objects/relationships.js +++ b/src/legacy/core_plugins/kibana/server/routes/api/management/saved_objects/relationships.js @@ -17,10 +17,8 @@ * under the License. */ -import Boom from 'boom'; import Joi from 'joi'; import { findRelationships } from '../../../../lib/management/saved_objects/relationships'; -import { isNotFoundError } from '../../../../../../../../server/saved_objects/service/lib/errors'; export function registerRelationships(server) { server.route({ @@ -33,7 +31,7 @@ export function registerRelationships(server) { id: Joi.string(), }), query: Joi.object().keys({ - size: Joi.number(), + size: Joi.number().default(10000), }), }, }, @@ -41,17 +39,15 @@ export function registerRelationships(server) { handler: async (req) => { const type = req.params.type; const id = req.params.id; - const size = req.query.size || 10; + const size = req.query.size; + const savedObjectsClient = req.getSavedObjectsClient(); - try { - return await findRelationships(type, id, size, req.getSavedObjectsClient()); - } catch (err) { - if (isNotFoundError(err)) { - throw Boom.boomify(new Error('Resource not found'), { statusCode: 404 }); - } - - throw Boom.boomify(err, { statusCode: 500 }); - } + return await findRelationships(type, id, { + size, + savedObjectsClient, + // Pass in all types except space, spaces wrapper will throw error + savedObjectTypes: server.savedObjects.types.filter(type => type !== 'space'), + }); }, }); } diff --git a/src/legacy/core_plugins/kibana/server/routes/api/management/saved_objects/scroll.js b/src/legacy/core_plugins/kibana/server/routes/api/management/saved_objects/scroll.js index 76cc66c05385c..a2cc63b4b8679 100644 --- a/src/legacy/core_plugins/kibana/server/routes/api/management/saved_objects/scroll.js +++ b/src/legacy/core_plugins/kibana/server/routes/api/management/saved_objects/scroll.js @@ -62,6 +62,7 @@ export function registerScrollForExportRoute(server) { savedObjectVersion: 2 }, _migrationVersion: hit.migrationVersion, + _references: hit.references || [], }; }); } diff --git a/src/legacy/core_plugins/kibana/server/tutorials/apm/saved_objects/index_pattern.json b/src/legacy/core_plugins/kibana/server/tutorials/apm/saved_objects/index_pattern.json index 917ed2abef878..961365e6393ff 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/apm/saved_objects/index_pattern.json +++ b/src/legacy/core_plugins/kibana/server/tutorials/apm/saved_objects/index_pattern.json @@ -1,7 +1,7 @@ { "attributes": { "fieldFormatMap": "{\"client.bytes\":{\"id\":\"bytes\"},\"destination.bytes\":{\"id\":\"bytes\"},\"event.duration\":{\"id\":\"duration\",\"params\":{\"inputFormat\":\"nanoseconds\"}},\"file.size\":{\"id\":\"bytes\"},\"http.request.body.bytes\":{\"id\":\"bytes\"},\"http.request.bytes\":{\"id\":\"bytes\"},\"http.response.body.bytes\":{\"id\":\"bytes\"},\"http.response.bytes\":{\"id\":\"bytes\"},\"network.bytes\":{\"id\":\"bytes\"},\"server.bytes\":{\"id\":\"bytes\"},\"source.bytes\":{\"id\":\"bytes\"},\"system.cpu.total.norm.pct\":{\"id\":\"percent\"},\"system.memory.actual.free\":{\"id\":\"bytes\"},\"system.memory.total\":{\"id\":\"bytes\"},\"system.process.cpu.total.norm.pct\":{\"id\":\"percent\"},\"system.process.memory.rss.bytes\":{\"id\":\"bytes\"},\"system.process.memory.size\":{\"id\":\"bytes\"},\"view spans\":{\"id\":\"url\",\"params\":{\"labelTemplate\":\"View Spans\"}}}", - "fields": "[{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"@timestamp\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tags\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"labels\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.ephemeral_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.ip\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.provider\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.availability_zone\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.region\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.instance.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.instance.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.machine.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.account.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.runtime\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.image.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.image.tag\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.labels\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.ip\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"ecs.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":4,\"doc_values\":true,\"indexed\":true,\"name\":\"error.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.kind\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.category\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.action\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.outcome\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.module\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.dataset\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.severity\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"event.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.duration\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.timezone\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.created\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.start\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.end\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.risk_score\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.risk_score_norm\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.target_path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.extension\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.device\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.inode\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.uid\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.owner\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.gid\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.group\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.mode\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.mtime\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.ctime\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.hostname\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.ip\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.architecture\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.method\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.body.content\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.referrer\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.status_code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.body.content\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.body.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.body.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.level\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"log.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.iana_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.transport\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.application\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.protocol\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.direction\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.forwarded_ip\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.community_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ip\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.hostname\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.vendor\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.serial_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"organization.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.ppid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.args\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.executable\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.title\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.thread.id\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.start\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.working_directory\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"related.ip\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.ip\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.state\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.ephemeral_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.ip\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.scheme\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.query\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.fragment\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.username\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.password\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.device.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.hostname\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.patch\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.minor\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.major\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.device\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.major\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.minor\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.hostname\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"fields\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.project.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"docker.container.labels\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.pod.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.pod.uid\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.namespace\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.node.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.labels\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.annotations\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.container.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.container.image\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"processor.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"processor.event\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"timestamp.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.finished\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.environment\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.language.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.language.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.runtime.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.runtime.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.framework.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.framework.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.sampled\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"trace.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"parent.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.listening\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.original.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.culprit\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.grouping_key\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.module\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":4,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.handled\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.level\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.logger_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.param_message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.cpu.total.norm.pct\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.memory.total\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.memory.actual.free\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.process.cpu.total.norm.pct\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.process.memory.size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.process.memory.rss.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"sourcemap.service.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"sourcemap.service.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"sourcemap.bundle_filepath\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"view spans\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.subtype\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.action\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.start.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.duration.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.sync\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.name.keyword\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.duration.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.result\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.marks\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.marks.*.*\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.span_count.dropped\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_id\",\"scripted\":false,\"searchable\":false,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_index\",\"scripted\":false,\"searchable\":false,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_score\",\"scripted\":false,\"searchable\":false,\"type\":\"number\"}]", + "fields": "[{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"@timestamp\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tags\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"labels\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.ephemeral_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.ip\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.provider\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.availability_zone\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.region\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.instance.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.instance.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.machine.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.account.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.runtime\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.image.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.image.tag\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.labels\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.ip\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"ecs.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":4,\"doc_values\":true,\"indexed\":true,\"name\":\"error.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.kind\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.category\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.action\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.outcome\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.module\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.dataset\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.severity\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"event.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.duration\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.timezone\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.created\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.start\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.end\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.risk_score\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.risk_score_norm\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.target_path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.extension\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.device\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.inode\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.uid\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.owner\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.gid\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.group\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.mode\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.mtime\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.ctime\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.hostname\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.ip\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.architecture\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.method\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.body.content\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.referrer\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.status_code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.body.content\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.body.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.body.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.level\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"log.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.iana_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.transport\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.application\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.protocol\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.direction\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.forwarded_ip\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.community_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ip\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.hostname\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.vendor\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.serial_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"organization.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.ppid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.args\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.executable\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.title\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.thread.id\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.start\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.working_directory\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"related.ip\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.ip\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.state\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.ephemeral_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.ip\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.scheme\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.query\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.fragment\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.username\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.password\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.device.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.hostname\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.patch\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.minor\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.major\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.device\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.major\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.minor\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.hostname\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"fields\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.project.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"docker.container.labels\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.pod.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.pod.uid\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.namespace\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.node.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.labels\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.annotations\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.container.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.container.image\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"processor.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"processor.event\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"timestamp.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.finished\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.environment\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.language.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.language.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.runtime.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.runtime.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.framework.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.framework.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.sampled\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"trace.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"parent.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.listening\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.version_major\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.original.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.culprit\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.grouping_key\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.module\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":4,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.handled\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.level\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.logger_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.param_message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.cpu.total.norm.pct\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.memory.total\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.memory.actual.free\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.process.cpu.total.norm.pct\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.process.memory.size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.process.memory.rss.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"sourcemap.service.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"sourcemap.service.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"sourcemap.bundle_filepath\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"view spans\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.subtype\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.action\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.start.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.duration.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.sync\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.duration.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.result\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.marks\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.marks.*.*\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.span_count.dropped\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_id\",\"scripted\":false,\"searchable\":false,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_index\",\"scripted\":false,\"searchable\":false,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_score\",\"scripted\":false,\"searchable\":false,\"type\":\"number\"}]", "sourceFilters": "[{\"value\":\"sourcemap.sourcemap\"}]", "timeFieldName": "@timestamp" }, diff --git a/src/legacy/core_plugins/kibana/server/tutorials/aws_metrics/index.js b/src/legacy/core_plugins/kibana/server/tutorials/aws_metrics/index.js new file mode 100644 index 0000000000000..9e22624ffcbe5 --- /dev/null +++ b/src/legacy/core_plugins/kibana/server/tutorials/aws_metrics/index.js @@ -0,0 +1,64 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { onPremInstructions, cloudInstructions, onPremCloudInstructions } from '../../../common/tutorials/metricbeat_instructions'; + +export function awsMetricsSpecProvider(server, context) { + const moduleName = 'aws'; + return { + id: 'awsMetrics', + name: i18n.translate('kbn.server.tutorials.awsMetrics.nameTitle', { + defaultMessage: 'AWS metrics', + }), + category: TUTORIAL_CATEGORY.METRICS, + shortDescription: i18n.translate('kbn.server.tutorials.awsMetrics.shortDescription', { + defaultMessage: 'Fetch monitoring metrics for EC2 instances from the AWS APIs and Cloudwatch.', + }), + longDescription: i18n.translate('kbn.server.tutorials.awsMetrics.longDescription', { + defaultMessage: 'The `aws` Metricbeat module fetches monitoring metrics from the AWS APIs and Cloudwatch. \ +[Learn more]({learnMoreLink}).', + values: { + learnMoreLink: '{config.docs.beats.metricbeat}/metricbeat-module-aws.html', + }, + }), + euiIconType: 'logoAWS', + isBeta: true, + artifacts: { + dashboards: [ + { + id: 'c5846400-f7fb-11e8-af03-c999c9dea608', + linkLabel: i18n.translate('kbn.server.tutorials.awsMetrics.artifacts.dashboards.linkLabel', { + defaultMessage: 'AWS metrics dashboard', + }), + isOverview: true + } + ], + exportedFields: { + documentationUrl: '{config.docs.beats.metricbeat}/exported-fields-aws.html' + } + }, + completionTimeMinutes: 10, + previewImagePath: '/plugins/kibana/home/tutorial_resources/aws_metrics/screenshot.png', + onPrem: onPremInstructions(moduleName, null, null, null, context), + elasticCloud: cloudInstructions(moduleName), + onPremElasticCloud: onPremCloudInstructions(moduleName) + }; +} diff --git a/src/legacy/core_plugins/kibana/server/tutorials/register.js b/src/legacy/core_plugins/kibana/server/tutorials/register.js index fd05018004657..4d757104294df 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/register.js +++ b/src/legacy/core_plugins/kibana/server/tutorials/register.js @@ -63,6 +63,7 @@ import { prometheusMetricsSpecProvider } from './prometheus_metrics'; import { zookeeperMetricsSpecProvider } from './zookeeper_metrics'; import { uptimeMonitorsSpecProvider } from './uptime_monitors'; import { cloudwatchLogsSpecProvider } from './cloudwatch_logs'; +import { awsMetricsSpecProvider } from './aws_metrics'; export function registerTutorials(server) { server.registerTutorial(systemLogsSpecProvider); @@ -111,4 +112,5 @@ export function registerTutorials(server) { server.registerTutorial(zookeeperMetricsSpecProvider); server.registerTutorial(uptimeMonitorsSpecProvider); server.registerTutorial(cloudwatchLogsSpecProvider); + server.registerTutorial(awsMetricsSpecProvider); } diff --git a/src/legacy/core_plugins/metrics/public/components/_error.scss b/src/legacy/core_plugins/metrics/public/components/_error.scss index 607863ccbf048..b9905c1011363 100644 --- a/src/legacy/core_plugins/metrics/public/components/_error.scss +++ b/src/legacy/core_plugins/metrics/public/components/_error.scss @@ -5,7 +5,7 @@ display: flex; align-items: center; justify-content: center; - + flex-direction: column; // Calculate colors similar to EuiCallout $tempBackgroundColor: tintOrShade($euiColorDanger, 90%, 70%); background-color: $tempBackgroundColor; diff --git a/src/legacy/core_plugins/metrics/public/kbn_vis_types/editor_controller.js b/src/legacy/core_plugins/metrics/public/kbn_vis_types/editor_controller.js index 89c859fdd421c..af40856fb20df 100644 --- a/src/legacy/core_plugins/metrics/public/kbn_vis_types/editor_controller.js +++ b/src/legacy/core_plugins/metrics/public/kbn_vis_types/editor_controller.js @@ -42,7 +42,8 @@ function ReactEditorControllerProvider(Private, config) { isEditorMode={true} appState={params.appState} /> - , this.el); + , + this.el); } resize() { diff --git a/src/legacy/core_plugins/metrics/public/kbn_vis_types/request_handler.js b/src/legacy/core_plugins/metrics/public/kbn_vis_types/request_handler.js index 59bc50b7d4562..08ec1d627fe1c 100644 --- a/src/legacy/core_plugins/metrics/public/kbn_vis_types/request_handler.js +++ b/src/legacy/core_plugins/metrics/public/kbn_vis_types/request_handler.js @@ -20,7 +20,6 @@ import { validateInterval } from '../lib/validate_interval'; import { timezoneProvider } from 'ui/vis/lib/timezone'; import { timefilter } from 'ui/timefilter'; -import { buildEsQuery } from '@kbn/es-query'; const MetricsRequestHandlerProvider = function (Private, Notifier, config, $http, i18n) { const notify = new Notifier({ location: i18n('tsvb.requestHandler.notifier.locationNameTitle', { defaultMessage: 'Metrics' }) }); @@ -35,14 +34,11 @@ const MetricsRequestHandlerProvider = function (Private, Notifier, config, $http const parsedTimeRange = timefilter.calculateBounds(timeRange); const scaledDataFormat = config.get('dateFormat:scaled'); const dateFormat = config.get('dateFormat'); - const esQueryConfigs = { - allowLeadingWildcards: config.get('query:allowLeadingWildcards'), - queryStringOptions: config.get('query:queryString:options'), - }; if (panel && panel.id) { const params = { timerange: { timezone, ...parsedTimeRange }, - filters: [buildEsQuery(undefined, query, filters, esQueryConfigs)], + query, + filters, panels: [panel], state: uiStateObj }; diff --git a/src/legacy/core_plugins/metrics/server/lib/vis_data/build_annotation_request.js b/src/legacy/core_plugins/metrics/server/lib/vis_data/build_annotation_request.js index 92902a999b43f..1796f3a0a7927 100644 --- a/src/legacy/core_plugins/metrics/server/lib/vis_data/build_annotation_request.js +++ b/src/legacy/core_plugins/metrics/server/lib/vis_data/build_annotation_request.js @@ -20,8 +20,8 @@ import buildProcessorFunction from './build_processor_function'; import processors from './request_processors/annotations'; -export default function buildAnnotationRequest(req, panel, annotation) { - const processor = buildProcessorFunction(processors, req, panel, annotation); +export default function buildAnnotationRequest(req, panel, annotation, esQueryConfig, indexPattern) { + const processor = buildProcessorFunction(processors, req, panel, annotation, esQueryConfig, indexPattern); const doc = processor({}); return doc; } diff --git a/src/legacy/core_plugins/metrics/server/lib/vis_data/get_annotations.js b/src/legacy/core_plugins/metrics/server/lib/vis_data/get_annotations.js index 96d65febf7657..cdb08f5289d88 100644 --- a/src/legacy/core_plugins/metrics/server/lib/vis_data/get_annotations.js +++ b/src/legacy/core_plugins/metrics/server/lib/vis_data/get_annotations.js @@ -19,6 +19,7 @@ import buildAnnotationRequest from './build_annotation_request'; import handleAnnotationResponse from './handle_annotation_response'; +import { getIndexPatternObject } from './helpers/get_index_pattern'; function validAnnotation(annotation) { return annotation.index_pattern && @@ -28,27 +29,19 @@ function validAnnotation(annotation) { annotation.template; } -export default async (req, panel) => { +export default async (req, panel, esQueryConfig) => { const { callWithRequest } = req.server.plugins.elasticsearch.getCluster('data'); - const bodies = panel.annotations + const bodiesPromises = panel.annotations .filter(validAnnotation) .map(annotation => { - - const indexPattern = annotation.index_pattern; - const bodies = []; - - bodies.push({ - index: indexPattern, - ignoreUnavailable: true, - }); - - const body = buildAnnotationRequest(req, panel, annotation); - body.timeout = '90s'; - bodies.push(body); - return bodies; + return getAnnotationBody(req, panel, annotation, esQueryConfig); }); - - if (!bodies.length) return { responses: [] }; + const bodies = await Promise.all(bodiesPromises); + if (!bodies.length) { + return { + responses: [], + }; + } try { const includeFrozen = await req.getUiSettingsService().get('search:includeFrozen'); const resp = await callWithRequest(req, 'msearch', { @@ -68,6 +61,18 @@ export default async (req, panel) => { if (error.message === 'missing-indices') return { responses: [] }; throw error; } - }; +async function getAnnotationBody(req, panel, annotation, esQueryConfig) { + const indexPatternString = annotation.index_pattern; + const indexPatternObject = await getIndexPatternObject(req, indexPatternString); + const request = buildAnnotationRequest(req, panel, annotation, esQueryConfig, indexPatternObject); + request.timeout = '90s'; + return [ + { + index: indexPatternString, + ignoreUnavailable: true, + }, + request, + ]; +} \ No newline at end of file diff --git a/src/legacy/core_plugins/metrics/server/lib/vis_data/get_panel_data.js b/src/legacy/core_plugins/metrics/server/lib/vis_data/get_panel_data.js index d22ca5e2ff4d9..865e97f370288 100644 --- a/src/legacy/core_plugins/metrics/server/lib/vis_data/get_panel_data.js +++ b/src/legacy/core_plugins/metrics/server/lib/vis_data/get_panel_data.js @@ -21,7 +21,9 @@ import { getTableData } from './get_table_data'; import { getSeriesData } from './get_series_data'; export default function getPanelData(req) { return panel => { - if (panel.type === 'table') return getTableData(req, panel); + if (panel.type === 'table') { + return getTableData(req, panel); + } return getSeriesData(req, panel); }; } diff --git a/src/legacy/core_plugins/metrics/server/lib/vis_data/get_series_data.js b/src/legacy/core_plugins/metrics/server/lib/vis_data/get_series_data.js index fe1eaf22a0a7a..2594db2c5db6d 100644 --- a/src/legacy/core_plugins/metrics/server/lib/vis_data/get_series_data.js +++ b/src/legacy/core_plugins/metrics/server/lib/vis_data/get_series_data.js @@ -21,36 +21,45 @@ import getRequestParams from './series/get_request_params'; import handleResponseBody from './series/handle_response_body'; import handleErrorResponse from './handle_error_response'; import getAnnotations from './get_annotations'; +import { getEsQueryConfig } from './helpers/get_es_query_uisettings'; + export async function getSeriesData(req, panel) { const { callWithRequest } = req.server.plugins.elasticsearch.getCluster('data'); const includeFrozen = await req.getUiSettingsService().get('search:includeFrozen'); - const bodies = panel.series.map(series => getRequestParams(req, panel, series)); - const params = { - rest_total_hits_as_int: true, - ignore_throttled: !includeFrozen, - body: bodies.reduce((acc, items) => acc.concat(items), []) - }; - return callWithRequest(req, 'msearch', params) - .then(resp => { - const series = resp.responses.map(handleResponseBody(panel)); - return { - [panel.id]: { - id: panel.id, - series: series.reduce((acc, series) => acc.concat(series), []) - } - }; - }) - .then(resp => { - if (!panel.annotations || panel.annotations.length === 0) return resp; - return getAnnotations(req, panel).then(annotations => { - resp[panel.id].annotations = annotations; + const esQueryConfig = await getEsQueryConfig(req); + + try { + const bodiesPromises = panel.series.map(series => getRequestParams(req, panel, series, esQueryConfig)); + const bodies = await Promise.all(bodiesPromises); + const params = { + rest_total_hits_as_int: true, + ignore_throttled: !includeFrozen, + body: bodies.reduce((acc, items) => acc.concat(items), []) + }; + return callWithRequest(req, 'msearch', params) + .then(resp => { + const series = resp.responses.map(handleResponseBody(panel)); + return { + [panel.id]: { + id: panel.id, + series: series.reduce((acc, series) => acc.concat(series), []) + } + }; + }) + .then(resp => { + if (!panel.annotations || panel.annotations.length === 0) return resp; + return getAnnotations(req, panel, esQueryConfig).then(annotations => { + resp[panel.id].annotations = annotations; + return resp; + }); + }) + .then(resp => { + resp.type = panel.type; return resp; - }); - }) - .then(resp => { - resp.type = panel.type; - return resp; - }) - .catch(handleErrorResponse(panel)); + }) + .catch(handleErrorResponse(panel)); + } catch(e) { + return handleErrorResponse(e); + } } diff --git a/src/legacy/core_plugins/metrics/server/lib/vis_data/get_table_data.js b/src/legacy/core_plugins/metrics/server/lib/vis_data/get_table_data.js index ad464505806c4..a979a6669e74d 100644 --- a/src/legacy/core_plugins/metrics/server/lib/vis_data/get_table_data.js +++ b/src/legacy/core_plugins/metrics/server/lib/vis_data/get_table_data.js @@ -16,18 +16,25 @@ * specific language governing permissions and limitations * under the License. */ - +import { get } from 'lodash'; import buildRequestBody from './table/build_request_body'; import handleErrorResponse from './handle_error_response'; -import { get } from 'lodash'; import processBucket from './table/process_bucket'; +import { getIndexPatternObject } from './helpers/get_index_pattern'; +import { getEsQueryConfig } from './helpers/get_es_query_uisettings'; + + export async function getTableData(req, panel) { const { callWithRequest } = req.server.plugins.elasticsearch.getCluster('data'); const includeFrozen = await req.getUiSettingsService().get('search:includeFrozen'); + const indexPatternString = panel.index_pattern; + + const esQueryConfig = await getEsQueryConfig(req); + const indexPatternObject = await getIndexPatternObject(req, indexPatternString); const params = { - index: panel.index_pattern, + index: indexPatternString, ignore_throttled: !includeFrozen, - body: buildRequestBody(req, panel) + body: buildRequestBody(req, panel, esQueryConfig, indexPatternObject) }; try { const resp = await callWithRequest(req, 'search', params); diff --git a/src/legacy/core_plugins/kibana/server/lib/export/collect_search_sources.js b/src/legacy/core_plugins/metrics/server/lib/vis_data/helpers/get_es_query_uisettings.js similarity index 53% rename from src/legacy/core_plugins/kibana/server/lib/export/collect_search_sources.js rename to src/legacy/core_plugins/metrics/server/lib/vis_data/helpers/get_es_query_uisettings.js index 38471e9b6012c..f5ee2335cd121 100644 --- a/src/legacy/core_plugins/kibana/server/lib/export/collect_search_sources.js +++ b/src/legacy/core_plugins/metrics/server/lib/vis_data/helpers/get_es_query_uisettings.js @@ -17,23 +17,12 @@ * under the License. */ -import { collectIndexPatterns } from './collect_index_patterns'; - -export async function collectSearchSources(savedObjectsClient, panels) { - const docs = panels.reduce((acc, panel) => { - const { savedSearchId } = panel.attributes; - if (savedSearchId) { - if (!acc.find(s => s.id === savedSearchId) && !panels.find(p => p.id === savedSearchId)) { - acc.push({ type: 'search', id: savedSearchId }); - } - } - return acc; - }, []); - - if (docs.length === 0) return []; - - const { saved_objects: savedObjects } = await savedObjectsClient.bulkGet(docs); - const indexPatterns = await collectIndexPatterns(savedObjectsClient, savedObjects); - - return savedObjects.concat(indexPatterns); +export async function getEsQueryConfig(req) { + const uiSettings = req.getUiSettingsService(); + const allowLeadingWildcards = await uiSettings.get('query:allowLeadingWildcards'); + const queryStringOptions = await uiSettings.get('query:queryString:options'); + return { + allowLeadingWildcards, + queryStringOptions: JSON.parse(queryStringOptions), + }; } diff --git a/src/legacy/core_plugins/kibana/server/lib/export/collect_index_patterns.js b/src/legacy/core_plugins/metrics/server/lib/vis_data/helpers/get_index_pattern.js similarity index 50% rename from src/legacy/core_plugins/kibana/server/lib/export/collect_index_patterns.js rename to src/legacy/core_plugins/metrics/server/lib/vis_data/helpers/get_index_pattern.js index 441871208dc08..391f36cd439ba 100644 --- a/src/legacy/core_plugins/kibana/server/lib/export/collect_index_patterns.js +++ b/src/legacy/core_plugins/metrics/server/lib/vis_data/helpers/get_index_pattern.js @@ -17,27 +17,25 @@ * under the License. */ -export async function collectIndexPatterns(savedObjectsClient, panels) { - const docs = panels.reduce((acc, panel) => { - const { kibanaSavedObjectMeta, savedSearchId } = panel.attributes; +export async function getIndexPatternObject(req, indexPatternString) { + // getting the matching index pattern + const savedObjectClient = req.getSavedObjectsClient(); + const indexPatternObjects = await savedObjectClient.find({ + type: 'index-pattern', + fields: ['title', 'fields'], + search: `"${indexPatternString}"`, + search_fields: ['title'], + }); - if (kibanaSavedObjectMeta && kibanaSavedObjectMeta.searchSourceJSON && !savedSearchId) { - let searchSourceData; - try { - searchSourceData = JSON.parse(kibanaSavedObjectMeta.searchSourceJSON); - } catch (err) { - return acc; - } - - if (searchSourceData.index && !acc.find(s => s.id === searchSourceData.index)) { - acc.push({ type: 'index-pattern', id: searchSourceData.index }); - } - } - return acc; - }, []); - - if (docs.length === 0) return []; - - const { saved_objects: savedObjects } = await savedObjectsClient.bulkGet(docs); - return savedObjects; + // getting the index pattern fields + const indexPatterns = indexPatternObjects.saved_objects + .filter(obj => obj.attributes.title === indexPatternString) + .map(indexPattern => { + const { title, fields } = indexPattern.attributes; + return { + title, + fields: JSON.parse(fields), + }; + }); + return indexPatterns.length === 1 ? indexPatterns[0] : null; } diff --git a/src/legacy/core_plugins/metrics/server/lib/vis_data/request_processors/annotations/query.js b/src/legacy/core_plugins/metrics/server/lib/vis_data/request_processors/annotations/query.js index 922a945883dd0..2e4f765b21321 100644 --- a/src/legacy/core_plugins/metrics/server/lib/vis_data/request_processors/annotations/query.js +++ b/src/legacy/core_plugins/metrics/server/lib/vis_data/request_processors/annotations/query.js @@ -19,29 +19,26 @@ import getBucketSize from '../../helpers/get_bucket_size'; import getTimerange from '../../helpers/get_timerange'; -export default function query(req, panel, annotation) { +import { buildEsQuery } from '@kbn/es-query'; + +export default function query(req, panel, annotation, esQueryConfig, indexPattern) { return next => doc => { const timeField = annotation.time_field; - const { - bucketSize - } = getBucketSize(req, 'auto'); + const { bucketSize } = getBucketSize(req, 'auto'); const { from, to } = getTimerange(req); doc.size = 0; - doc.query = { - bool: { - must: [] - } - }; - + const queries = !annotation.ignore_global_filters ? req.payload.query : []; + const filters = !annotation.ignore_global_filters ? req.payload.filters : []; + doc.query = buildEsQuery(indexPattern, queries, filters, esQueryConfig); const timerange = { range: { [timeField]: { gte: from.valueOf(), - lte: to.valueOf() - (bucketSize * 1000), + lte: to.valueOf() - bucketSize * 1000, format: 'epoch_millis', - } - } + }, + }, }; doc.query.bool.must.push(timerange); @@ -49,22 +46,17 @@ export default function query(req, panel, annotation) { doc.query.bool.must.push({ query_string: { query: annotation.query_string, - analyze_wildcard: true - } + analyze_wildcard: true, + }, }); } - const globalFilters = req.payload.filters; - if (!annotation.ignore_global_filters) { - doc.query.bool.must = doc.query.bool.must.concat(globalFilters); - } - if (!annotation.ignore_panel_filters && panel.filter) { doc.query.bool.must.push({ query_string: { query: panel.filter, - analyze_wildcard: true - } + analyze_wildcard: true, + }, }); } @@ -76,7 +68,5 @@ export default function query(req, panel, annotation) { } return next(doc); - }; } - diff --git a/src/legacy/core_plugins/metrics/server/lib/vis_data/request_processors/series/__tests__/query.js b/src/legacy/core_plugins/metrics/server/lib/vis_data/request_processors/series/__tests__/query.js index a889c8b3e96b6..dd18d1c865e1b 100644 --- a/src/legacy/core_plugins/metrics/server/lib/vis_data/request_processors/series/__tests__/query.js +++ b/src/legacy/core_plugins/metrics/server/lib/vis_data/request_processors/series/__tests__/query.js @@ -26,6 +26,10 @@ describe('query(req, panel, series)', () => { let panel; let series; let req; + const config = { + allowLeadingWildcards: true, + queryStringOptions: {}, + }; beforeEach(() => { req = { payload: { @@ -45,17 +49,18 @@ describe('query(req, panel, series)', () => { it('calls next when finished', () => { const next = sinon.spy(); - query(req, panel, series)(next)({}); + query(req, panel, series, config)(next)({}); expect(next.calledOnce).to.equal(true); }); it('returns doc with query for timerange', () => { const next = doc => doc; - const doc = query(req, panel, series)(next)({}); + const doc = query(req, panel, series, config)(next)({}); expect(doc).to.eql({ size: 0, query: { bool: { + filter: [], must: [ { range: { @@ -66,7 +71,9 @@ describe('query(req, panel, series)', () => { } } } - ] + ], + must_not: [], + should: [], } } }); @@ -75,11 +82,12 @@ describe('query(req, panel, series)', () => { it('returns doc with query for timerange (offset by 1h)', () => { series.offset_time = '1h'; const next = doc => doc; - const doc = query(req, panel, series)(next)({}); + const doc = query(req, panel, series, config)(next)({}); expect(doc).to.eql({ size: 0, query: { bool: { + filter: [], must: [ { range: { @@ -90,7 +98,9 @@ describe('query(req, panel, series)', () => { } } } - ] + ], + must_not: [], + should: [], } } }); @@ -111,21 +121,13 @@ describe('query(req, panel, series)', () => { } ]; const next = doc => doc; - const doc = query(req, panel, series)(next)({}); + const doc = query(req, panel, series, config)(next)({}); expect(doc).to.eql({ size: 0, query: { bool: { + filter: [], must: [ - { - range: { - timestamp: { - gte: 1483228800000, - lte: 1483232400000, - format: 'epoch_millis' - } - } - }, { bool: { must: [ @@ -136,8 +138,19 @@ describe('query(req, panel, series)', () => { } ] } - } - ] + }, + { + range: { + timestamp: { + gte: 1483228800000, + lte: 1483232400000, + format: 'epoch_millis' + } + } + }, + ], + must_not: [], + should: [], } } }); @@ -146,11 +159,12 @@ describe('query(req, panel, series)', () => { it('returns doc with series filter', () => { series.filter = 'host:web-server'; const next = doc => doc; - const doc = query(req, panel, series)(next)({}); + const doc = query(req, panel, series, config)(next)({}); expect(doc).to.eql({ size: 0, query: { bool: { + filter: [], must: [ { range: { @@ -166,8 +180,10 @@ describe('query(req, panel, series)', () => { query: series.filter, analyze_wildcard: true } - } - ] + }, + ], + must_not: [], + should: [], } } }); @@ -188,21 +204,13 @@ describe('query(req, panel, series)', () => { ]; panel.filter = 'host:web-server'; const next = doc => doc; - const doc = query(req, panel, series)(next)({}); + const doc = query(req, panel, series, config)(next)({}); expect(doc).to.eql({ size: 0, query: { bool: { + filter: [], must: [ - { - range: { - timestamp: { - gte: 1483228800000, - lte: 1483232400000, - format: 'epoch_millis' - } - } - }, { bool: { must: [ @@ -214,13 +222,24 @@ describe('query(req, panel, series)', () => { ] } }, + { + range: { + timestamp: { + gte: 1483228800000, + lte: 1483232400000, + format: 'epoch_millis' + } + } + }, { query_string: { query: panel.filter, analyze_wildcard: true } } - ] + ], + must_not: [], + should: [], } } }); @@ -243,11 +262,12 @@ describe('query(req, panel, series)', () => { panel.filter = 'host:web-server'; panel.ignore_global_filter = true; const next = doc => doc; - const doc = query(req, panel, series)(next)({}); + const doc = query(req, panel, series, config)(next)({}); expect(doc).to.eql({ size: 0, query: { bool: { + filter: [], must: [ { range: { @@ -263,8 +283,10 @@ describe('query(req, panel, series)', () => { query: panel.filter, analyze_wildcard: true } - } - ] + }, + ], + must_not: [], + should: [], } } }); diff --git a/src/legacy/core_plugins/metrics/server/lib/vis_data/request_processors/series/query.js b/src/legacy/core_plugins/metrics/server/lib/vis_data/request_processors/series/query.js index 65813ff42a808..9a08aec011cc5 100644 --- a/src/legacy/core_plugins/metrics/server/lib/vis_data/request_processors/series/query.js +++ b/src/legacy/core_plugins/metrics/server/lib/vis_data/request_processors/series/query.js @@ -19,17 +19,17 @@ import offsetTime from '../../offset_time'; import getIntervalAndTimefield from '../../get_interval_and_timefield'; -export default function query(req, panel, series) { +import { buildEsQuery } from '@kbn/es-query'; + +export default function query(req, panel, series, esQueryConfig, indexPattern) { return next => doc => { const { timeField } = getIntervalAndTimefield(panel, series); const { from, to } = offsetTime(req, series.offset_time); doc.size = 0; - doc.query = { - bool: { - must: [] - } - }; + const queries = !panel.ignore_global_filter ? req.payload.query : []; + const filters = !panel.ignore_global_filter ? req.payload.filters : []; + doc.query = buildEsQuery(indexPattern, queries, filters, esQueryConfig); const timerange = { range: { @@ -37,22 +37,17 @@ export default function query(req, panel, series) { gte: from.valueOf(), lte: to.valueOf(), format: 'epoch_millis', - } - } + }, + }, }; doc.query.bool.must.push(timerange); - const globalFilters = req.payload.filters; - if (globalFilters && !panel.ignore_global_filter) { - doc.query.bool.must = doc.query.bool.must.concat(globalFilters); - } - if (panel.filter) { doc.query.bool.must.push({ query_string: { query: panel.filter, - analyze_wildcard: true - } + analyze_wildcard: true, + }, }); } @@ -60,12 +55,11 @@ export default function query(req, panel, series) { doc.query.bool.must.push({ query_string: { query: series.filter, - analyze_wildcard: true - } + analyze_wildcard: true, + }, }); } return next(doc); - }; } diff --git a/src/legacy/core_plugins/metrics/server/lib/vis_data/request_processors/table/query.js b/src/legacy/core_plugins/metrics/server/lib/vis_data/request_processors/table/query.js index 77a970003d93c..ece96f19c3607 100644 --- a/src/legacy/core_plugins/metrics/server/lib/vis_data/request_processors/table/query.js +++ b/src/legacy/core_plugins/metrics/server/lib/vis_data/request_processors/table/query.js @@ -16,20 +16,20 @@ * specific language governing permissions and limitations * under the License. */ - +import { buildEsQuery } from '@kbn/es-query'; import getTimerange from '../../helpers/get_timerange'; import getIntervalAndTimefield from '../../get_interval_and_timefield'; -export default function query(req, panel) { + +export default function query(req, panel, esQueryConfig, indexPattern) { return next => doc => { const { timeField } = getIntervalAndTimefield(panel); const { from, to } = getTimerange(req); doc.size = 0; - doc.query = { - bool: { - must: [] - } - }; + + const queries = !panel.ignore_global_filter ? req.payload.query : []; + const filters = !panel.ignore_global_filter ? req.payload.filters : []; + doc.query = buildEsQuery(indexPattern, queries, filters, esQueryConfig); const timerange = { range: { @@ -37,26 +37,20 @@ export default function query(req, panel) { gte: from.valueOf(), lte: to.valueOf(), format: 'epoch_millis', - } - } + }, + }, }; doc.query.bool.must.push(timerange); - const globalFilters = req.payload.filters; - if (globalFilters && !panel.ignore_global_filter) { - doc.query.bool.must = doc.query.bool.must.concat(globalFilters); - } - if (panel.filter) { doc.query.bool.must.push({ query_string: { query: panel.filter, - analyze_wildcard: true - } + analyze_wildcard: true, + }, }); } return next(doc); - }; } diff --git a/src/legacy/core_plugins/metrics/server/lib/vis_data/series/__tests__/build_request_body.js b/src/legacy/core_plugins/metrics/server/lib/vis_data/series/__tests__/build_request_body.js index 31699e3c41831..cb944bb8363fa 100644 --- a/src/legacy/core_plugins/metrics/server/lib/vis_data/series/__tests__/build_request_body.js +++ b/src/legacy/core_plugins/metrics/server/lib/vis_data/series/__tests__/build_request_body.js @@ -82,21 +82,17 @@ describe('buildRequestBody(req)', () => { it('returns a valid body', () => { const panel = body.panels[0]; const series = panel.series[0]; - const doc = buildRequestBody({ payload: body }, panel, series); + const config = { + allowLeadingWildcards: true, + queryStringOptions: {}, + }; + const doc = buildRequestBody({ payload: body }, panel, series, config); expect(doc).to.eql({ size: 0, query: { bool: { + filter: [], must: [ - { - range: { - '@timestamp': { - gte: 1485463055881, - lte: 1485463955881, - format: 'epoch_millis' - } - } - }, { bool: { must: [ @@ -109,8 +105,19 @@ describe('buildRequestBody(req)', () => { ], must_not: [] } - } - ] + }, + { + range: { + '@timestamp': { + gte: 1485463055881, + lte: 1485463955881, + format: 'epoch_millis' + } + } + }, + ], + must_not: [], + should: [], } }, aggs: { diff --git a/src/legacy/core_plugins/metrics/server/lib/vis_data/series/build_request_body.js b/src/legacy/core_plugins/metrics/server/lib/vis_data/series/build_request_body.js index 0afe2869b72fa..c25127b68a6bc 100644 --- a/src/legacy/core_plugins/metrics/server/lib/vis_data/series/build_request_body.js +++ b/src/legacy/core_plugins/metrics/server/lib/vis_data/series/build_request_body.js @@ -20,8 +20,8 @@ import buildProcessorFunction from '../build_processor_function'; import processors from '../request_processors/series'; -function buildRequestBody(req, panel, series) { - const processor = buildProcessorFunction(processors, req, panel, series); +function buildRequestBody(req, panel, series, esQueryConfig, indexPattern) { + const processor = buildProcessorFunction(processors, req, panel, series, esQueryConfig, indexPattern); const doc = processor({}); return doc; } diff --git a/src/legacy/core_plugins/metrics/server/lib/vis_data/series/get_request_params.js b/src/legacy/core_plugins/metrics/server/lib/vis_data/series/get_request_params.js index ed56035d859b8..598ebed73b94c 100644 --- a/src/legacy/core_plugins/metrics/server/lib/vis_data/series/get_request_params.js +++ b/src/legacy/core_plugins/metrics/server/lib/vis_data/series/get_request_params.js @@ -18,18 +18,18 @@ */ import buildRequestBody from './build_request_body'; +import { getIndexPatternObject } from '../helpers/get_index_pattern'; -export default (req, panel, series) => { - const indexPattern = series.override_index_pattern && series.series_index_pattern || panel.index_pattern; - const bodies = []; - - bodies.push({ - index: indexPattern, - ignoreUnavailable: true, - }); - - const body = buildRequestBody(req, panel, series); - body.timeout = '90s'; - bodies.push(body); - return bodies; +export default async (req, panel, series, esQueryConfig) => { + const indexPatternString = series.override_index_pattern && series.series_index_pattern || panel.index_pattern; + const indexPatternObject = await getIndexPatternObject(req, indexPatternString); + const request = buildRequestBody(req, panel, series, esQueryConfig, indexPatternObject); + request.timeout = '90s'; + return [ + { + index: indexPatternString, + ignoreUnavailable: true, + }, + request, + ]; }; diff --git a/src/legacy/core_plugins/metrics/server/lib/vis_data/table/build_request_body.js b/src/legacy/core_plugins/metrics/server/lib/vis_data/table/build_request_body.js index 8a71a7ba79f0a..7e509a5ebf2e5 100644 --- a/src/legacy/core_plugins/metrics/server/lib/vis_data/table/build_request_body.js +++ b/src/legacy/core_plugins/metrics/server/lib/vis_data/table/build_request_body.js @@ -20,8 +20,8 @@ import buildProcessorFunction from '../build_processor_function'; import processors from '../request_processors/table'; -function buildRequestBody(req, panel) { - const processor = buildProcessorFunction(processors, req, panel); +function buildRequestBody(req, panel, esQueryConfig, indexPattern) { + const processor = buildProcessorFunction(processors, req, panel, esQueryConfig, indexPattern); const doc = processor({}); return doc; } diff --git a/src/server/i18n/index.js b/src/server/i18n/index.js index 467b545d3b2d2..fff394f5bb21c 100644 --- a/src/server/i18n/index.js +++ b/src/server/i18n/index.js @@ -26,7 +26,7 @@ import { fromRoot } from '../../utils'; export async function i18nMixin(kbnServer, server, config) { const locale = config.get('i18n.locale'); - const translationsDirs = [fromRoot('src/ui/translations'), fromRoot('src/server/translations')]; + const translationsDirs = [fromRoot('src/ui/translations'), fromRoot('src/server/translations'), fromRoot('src/core/translations')]; const groupedEntries = await Promise.all([ ...config.get('plugins.scanDirs').map(async path => { diff --git a/src/server/saved_objects/migrations/core/__snapshots__/build_active_mappings.test.ts.snap b/src/server/saved_objects/migrations/core/__snapshots__/build_active_mappings.test.ts.snap index 156c5fa6887f4..7e6b8bf5e9e58 100644 --- a/src/server/saved_objects/migrations/core/__snapshots__/build_active_mappings.test.ts.snap +++ b/src/server/saved_objects/migrations/core/__snapshots__/build_active_mappings.test.ts.snap @@ -25,6 +25,20 @@ Object { "namespace": Object { "type": "keyword", }, + "references": Object { + "properties": Object { + "id": Object { + "type": "keyword", + }, + "name": Object { + "type": "keyword", + }, + "type": Object { + "type": "keyword", + }, + }, + "type": "nested", + }, "type": Object { "type": "keyword", }, diff --git a/src/server/saved_objects/migrations/core/build_active_mappings.ts b/src/server/saved_objects/migrations/core/build_active_mappings.ts index 156f47faecd56..eae75b418ed54 100644 --- a/src/server/saved_objects/migrations/core/build_active_mappings.ts +++ b/src/server/saved_objects/migrations/core/build_active_mappings.ts @@ -74,6 +74,20 @@ function defaultMapping(): IndexMapping { updated_at: { type: 'date', }, + references: { + type: 'nested', + properties: { + name: { + type: 'keyword', + }, + type: { + type: 'keyword', + }, + id: { + type: 'keyword', + }, + }, + }, }, }; } diff --git a/src/server/saved_objects/migrations/core/document_migrator.test.ts b/src/server/saved_objects/migrations/core/document_migrator.test.ts index cdd895594e4dd..293d96c8e62f4 100644 --- a/src/server/saved_objects/migrations/core/document_migrator.test.ts +++ b/src/server/saved_objects/migrations/core/document_migrator.test.ts @@ -19,7 +19,7 @@ import _ from 'lodash'; import sinon from 'sinon'; -import { SavedObjectDoc } from '../../serialization'; +import { RawSavedObjectDoc } from '../../serialization'; import { DocumentMigrator } from './document_migrator'; describe('DocumentMigrator', () => { @@ -486,13 +486,13 @@ describe('DocumentMigrator', () => { ...testOpts(), migrations: { aaa: { - '1.2.3': (doc: SavedObjectDoc) => doc, - '10.4.0': (doc: SavedObjectDoc) => doc, - '2.2.1': (doc: SavedObjectDoc) => doc, + '1.2.3': (doc: RawSavedObjectDoc) => doc, + '10.4.0': (doc: RawSavedObjectDoc) => doc, + '2.2.1': (doc: RawSavedObjectDoc) => doc, }, bbb: { - '3.2.3': (doc: SavedObjectDoc) => doc, - '2.0.0': (doc: SavedObjectDoc) => doc, + '3.2.3': (doc: RawSavedObjectDoc) => doc, + '2.0.0': (doc: RawSavedObjectDoc) => doc, }, }, }); @@ -525,11 +525,11 @@ describe('DocumentMigrator', () => { }); function renameAttr(path: string, newPath: string) { - return (doc: SavedObjectDoc) => - _.omit(_.set(doc, newPath, _.get(doc, path)), path) as SavedObjectDoc; + return (doc: RawSavedObjectDoc) => + _.omit(_.set(doc, newPath, _.get(doc, path)), path) as RawSavedObjectDoc; } function setAttr(path: string, value: any) { - return (doc: SavedObjectDoc) => - _.set(doc, path, _.isFunction(value) ? value(_.get(doc, path)) : value) as SavedObjectDoc; + return (doc: RawSavedObjectDoc) => + _.set(doc, path, _.isFunction(value) ? value(_.get(doc, path)) : value) as RawSavedObjectDoc; } diff --git a/src/server/saved_objects/migrations/core/document_migrator.ts b/src/server/saved_objects/migrations/core/document_migrator.ts index 0e0631aa8fce2..4a2a5aedc250d 100644 --- a/src/server/saved_objects/migrations/core/document_migrator.ts +++ b/src/server/saved_objects/migrations/core/document_migrator.ts @@ -63,12 +63,12 @@ import Boom from 'boom'; import _ from 'lodash'; import Semver from 'semver'; -import { MigrationVersion, SavedObjectDoc } from '../../serialization'; +import { MigrationVersion, RawSavedObjectDoc } from '../../serialization'; import { LogFn, Logger, MigrationLogger } from './migration_logger'; -export type TransformFn = (doc: SavedObjectDoc) => SavedObjectDoc; +export type TransformFn = (doc: RawSavedObjectDoc) => RawSavedObjectDoc; -type ValidateDoc = (doc: SavedObjectDoc) => void; +type ValidateDoc = (doc: RawSavedObjectDoc) => void; interface MigrationDefinition { [type: string]: { [version: string]: TransformFn }; @@ -142,11 +142,11 @@ export class DocumentMigrator implements VersionedTransformer { /** * Migrates a document to the latest version. * - * @param {SavedObjectDoc} doc - * @returns {SavedObjectDoc} + * @param {RawSavedObjectDoc} doc + * @returns {RawSavedObjectDoc} * @memberof DocumentMigrator */ - public migrate = (doc: SavedObjectDoc): SavedObjectDoc => { + public migrate = (doc: RawSavedObjectDoc): RawSavedObjectDoc => { return this.transformDoc(doc); }; } @@ -225,7 +225,7 @@ function buildDocumentTransform({ migrations: ActiveMigrations; validateDoc: ValidateDoc; }): TransformFn { - return function transformAndValidate(doc: SavedObjectDoc) { + return function transformAndValidate(doc: RawSavedObjectDoc) { const result = doc.migrationVersion ? applyMigrations(doc, migrations) : markAsUpToDate(doc, migrations); @@ -243,7 +243,7 @@ function buildDocumentTransform({ }; } -function applyMigrations(doc: SavedObjectDoc, migrations: ActiveMigrations) { +function applyMigrations(doc: RawSavedObjectDoc, migrations: ActiveMigrations) { while (true) { const prop = nextUnmigratedProp(doc, migrations); if (!prop) { @@ -256,14 +256,14 @@ function applyMigrations(doc: SavedObjectDoc, migrations: ActiveMigrations) { /** * Gets the doc's props, handling the special case of "type". */ -function props(doc: SavedObjectDoc) { +function props(doc: RawSavedObjectDoc) { return Object.keys(doc).concat(doc.type); } /** * Looks up the prop version in a saved object document or in our latest migrations. */ -function propVersion(doc: SavedObjectDoc | ActiveMigrations, prop: string) { +function propVersion(doc: RawSavedObjectDoc | ActiveMigrations, prop: string) { return ( (doc[prop] && doc[prop].latestVersion) || (doc.migrationVersion && (doc as any).migrationVersion[prop]) @@ -273,7 +273,7 @@ function propVersion(doc: SavedObjectDoc | ActiveMigrations, prop: string) { /** * Sets the doc's migrationVersion to be the most recent version */ -function markAsUpToDate(doc: SavedObjectDoc, migrations: ActiveMigrations) { +function markAsUpToDate(doc: RawSavedObjectDoc, migrations: ActiveMigrations) { return { ...doc, migrationVersion: props(doc).reduce((acc, prop) => { @@ -288,7 +288,7 @@ function markAsUpToDate(doc: SavedObjectDoc, migrations: ActiveMigrations) { * about the document and transform that caused the failure. */ function wrapWithTry(version: string, prop: string, transform: TransformFn, log: Logger) { - return function tryTransformDoc(doc: SavedObjectDoc) { + return function tryTransformDoc(doc: RawSavedObjectDoc) { try { const result = transform(doc); @@ -313,7 +313,7 @@ function wrapWithTry(version: string, prop: string, transform: TransformFn, log: /** * Finds the first unmigrated property in the specified document. */ -function nextUnmigratedProp(doc: SavedObjectDoc, migrations: ActiveMigrations) { +function nextUnmigratedProp(doc: RawSavedObjectDoc, migrations: ActiveMigrations) { return props(doc).find(p => { const latestVersion = propVersion(migrations, p); const docVersion = propVersion(doc, p); @@ -343,10 +343,10 @@ function nextUnmigratedProp(doc: SavedObjectDoc, migrations: ActiveMigrations) { * Applies any relevent migrations to the document for the specified property. */ function migrateProp( - doc: SavedObjectDoc, + doc: RawSavedObjectDoc, prop: string, migrations: ActiveMigrations -): SavedObjectDoc { +): RawSavedObjectDoc { const originalType = doc.type; let migrationVersion = _.clone(doc.migrationVersion) || {}; const typeChanged = () => !doc.hasOwnProperty(prop) || doc.type !== originalType; @@ -367,7 +367,7 @@ function migrateProp( /** * Retrieves any prop transforms that have not been applied to doc. */ -function applicableTransforms(migrations: ActiveMigrations, doc: SavedObjectDoc, prop: string) { +function applicableTransforms(migrations: ActiveMigrations, doc: RawSavedObjectDoc, prop: string) { const minVersion = propVersion(doc, prop); const { transforms } = migrations[prop]; return minVersion @@ -380,7 +380,7 @@ function applicableTransforms(migrations: ActiveMigrations, doc: SavedObjectDoc, * has not mutated migrationVersion in an unsupported way. */ function updateMigrationVersion( - doc: SavedObjectDoc, + doc: RawSavedObjectDoc, migrationVersion: MigrationVersion, prop: string, version: string @@ -396,7 +396,7 @@ function updateMigrationVersion( * as this could get us into an infinite loop. So, we explicitly check for that here. */ function assertNoDowngrades( - doc: SavedObjectDoc, + doc: RawSavedObjectDoc, migrationVersion: MigrationVersion, prop: string, version: string diff --git a/src/server/saved_objects/migrations/core/index_migrator.test.ts b/src/server/saved_objects/migrations/core/index_migrator.test.ts index a5be8e5216773..68e13bfbb53e2 100644 --- a/src/server/saved_objects/migrations/core/index_migrator.test.ts +++ b/src/server/saved_objects/migrations/core/index_migrator.test.ts @@ -20,7 +20,7 @@ import _ from 'lodash'; import sinon from 'sinon'; import { SavedObjectsSchema } from '../../schema'; -import { SavedObjectDoc, SavedObjectsSerializer } from '../../serialization'; +import { RawSavedObjectDoc, SavedObjectsSerializer } from '../../serialization'; import { CallCluster } from './call_cluster'; import { IndexMigrator } from './index_migrator'; @@ -50,6 +50,14 @@ describe('IndexMigrator', () => { namespace: { type: 'keyword' }, type: { type: 'keyword' }, updated_at: { type: 'date' }, + references: { + type: 'nested', + properties: { + name: { type: 'keyword' }, + type: { type: 'keyword' }, + id: { type: 'keyword' }, + }, + }, }, }, include_type_name: true, @@ -83,6 +91,14 @@ describe('IndexMigrator', () => { namespace: { type: 'keyword' }, type: { type: 'keyword' }, updated_at: { type: 'date' }, + references: { + type: 'nested', + properties: { + name: { type: 'keyword' }, + type: { type: 'keyword' }, + id: { type: 'keyword' }, + }, + }, }, }, settings: { number_of_shards: 1, auto_expand_replicas: '0-1' }, @@ -191,6 +207,14 @@ describe('IndexMigrator', () => { namespace: { type: 'keyword' }, type: { type: 'keyword' }, updated_at: { type: 'date' }, + references: { + type: 'nested', + properties: { + name: { type: 'keyword' }, + type: { type: 'keyword' }, + id: { type: 'keyword' }, + }, + }, }, }, settings: { number_of_shards: 1, auto_expand_replicas: '0-1' }, @@ -240,7 +264,7 @@ describe('IndexMigrator', () => { let count = 0; const opts = defaultOpts(); const callCluster = clusterStub(opts); - const migrateDoc = sinon.spy((doc: SavedObjectDoc) => ({ + const migrateDoc = sinon.spy((doc: RawSavedObjectDoc) => ({ ...doc, attributes: { name: ++count }, })); @@ -266,24 +290,26 @@ describe('IndexMigrator', () => { type: 'foo', attributes: { name: 'Bar' }, migrationVersion: {}, + references: [], }); sinon.assert.calledWith(migrateDoc, { id: '2', type: 'foo', attributes: { name: 'Baz' }, migrationVersion: {}, + references: [], }); expect(callCluster.args.filter(([action]) => action === 'bulk').length).toEqual(2); sinon.assert.calledWith(callCluster, 'bulk', { body: [ { index: { _id: 'foo:1', _index: '.kibana_2' } }, - { foo: { name: 1 }, type: 'foo', migrationVersion: {} }, + { foo: { name: 1 }, type: 'foo', migrationVersion: {}, references: [] }, ], }); sinon.assert.calledWith(callCluster, 'bulk', { body: [ { index: { _id: 'foo:2', _index: '.kibana_2' } }, - { foo: { name: 2 }, type: 'foo', migrationVersion: {} }, + { foo: { name: 2 }, type: 'foo', migrationVersion: {}, references: [] }, ], }); }); diff --git a/src/server/saved_objects/migrations/core/migrate_raw_docs.test.ts b/src/server/saved_objects/migrations/core/migrate_raw_docs.test.ts index 01bd409da8b69..26ccb4085e2a9 100644 --- a/src/server/saved_objects/migrations/core/migrate_raw_docs.test.ts +++ b/src/server/saved_objects/migrations/core/migrate_raw_docs.test.ts @@ -34,11 +34,11 @@ describe('migrateRawDocs', () => { expect(result).toEqual([ { _id: 'a:b', - _source: { type: 'a', a: { name: 'HOI!' }, migrationVersion: {} }, + _source: { type: 'a', a: { name: 'HOI!' }, migrationVersion: {}, references: [] }, }, { _id: 'c:d', - _source: { type: 'c', c: { name: 'HOI!' }, migrationVersion: {} }, + _source: { type: 'c', c: { name: 'HOI!' }, migrationVersion: {}, references: [] }, }, ]); @@ -56,7 +56,7 @@ describe('migrateRawDocs', () => { { _id: 'foo:b', _source: { type: 'a', a: { name: 'AAA' } } }, { _id: 'c:d', - _source: { type: 'c', c: { name: 'TADA' }, migrationVersion: {} }, + _source: { type: 'c', c: { name: 'TADA' }, migrationVersion: {}, references: [] }, }, ]); @@ -69,6 +69,7 @@ describe('migrateRawDocs', () => { name: 'DDD', }, migrationVersion: {}, + references: [], }, ], ]); diff --git a/src/server/saved_objects/migrations/core/migrate_raw_docs.ts b/src/server/saved_objects/migrations/core/migrate_raw_docs.ts index ffafa9908d23e..c008b18619629 100644 --- a/src/server/saved_objects/migrations/core/migrate_raw_docs.ts +++ b/src/server/saved_objects/migrations/core/migrate_raw_docs.ts @@ -41,7 +41,10 @@ export function migrateRawDocs( if (serializer.isRawSavedObject(raw)) { const savedObject = serializer.rawToSavedObject(raw); savedObject.migrationVersion = savedObject.migrationVersion || {}; - return serializer.savedObjectToRaw(migrateDoc(savedObject)); + return serializer.savedObjectToRaw({ + references: [], + ...migrateDoc(savedObject), + }); } return raw; diff --git a/src/server/saved_objects/migrations/kibana/__snapshots__/kibana_migrator.test.ts.snap b/src/server/saved_objects/migrations/kibana/__snapshots__/kibana_migrator.test.ts.snap index 1733f94ded552..9627a351f55d4 100644 --- a/src/server/saved_objects/migrations/kibana/__snapshots__/kibana_migrator.test.ts.snap +++ b/src/server/saved_objects/migrations/kibana/__snapshots__/kibana_migrator.test.ts.snap @@ -25,6 +25,20 @@ Object { "namespace": Object { "type": "keyword", }, + "references": Object { + "properties": Object { + "id": Object { + "type": "keyword", + }, + "name": Object { + "type": "keyword", + }, + "type": Object { + "type": "keyword", + }, + }, + "type": "nested", + }, "type": Object { "type": "keyword", }, diff --git a/src/server/saved_objects/migrations/kibana/kibana_migrator.ts b/src/server/saved_objects/migrations/kibana/kibana_migrator.ts index 06ff383133d35..3eddd46586d6b 100644 --- a/src/server/saved_objects/migrations/kibana/kibana_migrator.ts +++ b/src/server/saved_objects/migrations/kibana/kibana_migrator.ts @@ -24,7 +24,7 @@ import { once } from 'lodash'; import { SavedObjectsSchema, SavedObjectsSchemaDefinition } from '../../schema'; -import { SavedObjectDoc, SavedObjectsSerializer } from '../../serialization'; +import { RawSavedObjectDoc, SavedObjectsSerializer } from '../../serialization'; import { docValidator } from '../../validation'; import { buildActiveMappings, CallCluster, IndexMigrator, LogFn, MappingProperties } from '../core'; import { DocumentMigrator, VersionedTransformer } from '../core/document_migrator'; @@ -146,11 +146,11 @@ export class KibanaMigrator { /** * Migrates an individual doc to the latest version, as defined by the plugin migrations. * - * @param {SavedObjectDoc} doc - * @returns {SavedObjectDoc} + * @param {RawSavedObjectDoc} doc + * @returns {RawSavedObjectDoc} * @memberof KibanaMigrator */ - public migrateDocument(doc: SavedObjectDoc): SavedObjectDoc { + public migrateDocument(doc: RawSavedObjectDoc): RawSavedObjectDoc { return this.documentMigrator.migrate(doc); } } diff --git a/src/server/saved_objects/routes/bulk_create.js b/src/server/saved_objects/routes/bulk_create.js index 943b2520a331d..d2dc14e3f6f7b 100644 --- a/src/server/saved_objects/routes/bulk_create.js +++ b/src/server/saved_objects/routes/bulk_create.js @@ -37,6 +37,14 @@ export const createBulkCreateRoute = prereqs => ({ attributes: Joi.object().required(), version: Joi.number(), migrationVersion: Joi.object().optional(), + references: Joi.array().items( + Joi.object() + .keys({ + name: Joi.string().required(), + type: Joi.string().required(), + id: Joi.string().required(), + }), + ).default([]), }).required() ), }, diff --git a/src/server/saved_objects/routes/bulk_get.test.js b/src/server/saved_objects/routes/bulk_get.test.js index e646629970fd4..62c155a57a165 100644 --- a/src/server/saved_objects/routes/bulk_get.test.js +++ b/src/server/saved_objects/routes/bulk_get.test.js @@ -59,7 +59,8 @@ describe('POST /api/saved_objects/_bulk_get', () => { id: 'abc123', type: 'index-pattern', title: 'logstash-*', - version: 2 + version: 2, + references: [], }] }; diff --git a/src/server/saved_objects/routes/create.js b/src/server/saved_objects/routes/create.js index 46d47d0f33891..a93e23a57550b 100644 --- a/src/server/saved_objects/routes/create.js +++ b/src/server/saved_objects/routes/create.js @@ -40,14 +40,22 @@ export const createCreateRoute = prereqs => { payload: Joi.object({ attributes: Joi.object().required(), migrationVersion: Joi.object().optional(), + references: Joi.array().items( + Joi.object() + .keys({ + name: Joi.string().required(), + type: Joi.string().required(), + id: Joi.string().required(), + }), + ).default([]), }).required(), }, handler(request) { const { savedObjectsClient } = request.pre; const { type, id } = request.params; const { overwrite } = request.query; - const { migrationVersion } = request.payload; - const options = { id, overwrite, migrationVersion }; + const { migrationVersion, references } = request.payload; + const options = { id, overwrite, migrationVersion, references }; return savedObjectsClient.create(type, request.payload.attributes, options); }, diff --git a/src/server/saved_objects/routes/create.test.js b/src/server/saved_objects/routes/create.test.js index 48b6cb970e56b..4e35e2e3b38d4 100644 --- a/src/server/saved_objects/routes/create.test.js +++ b/src/server/saved_objects/routes/create.test.js @@ -57,7 +57,8 @@ describe('POST /api/saved_objects/{type}', () => { const clientResponse = { type: 'index-pattern', id: 'logstash-*', - title: 'Testing' + title: 'Testing', + references: [], }; savedObjectsClient.create.returns(Promise.resolve(clientResponse)); @@ -100,7 +101,7 @@ describe('POST /api/saved_objects/{type}', () => { expect(savedObjectsClient.create.calledOnce).toBe(true); const args = savedObjectsClient.create.getCall(0).args; - const options = { overwrite: false, id: undefined, migrationVersion: undefined }; + const options = { overwrite: false, id: undefined, migrationVersion: undefined, references: [] }; const attributes = { title: 'Testing' }; expect(args).toEqual(['index-pattern', attributes, options]); @@ -121,7 +122,7 @@ describe('POST /api/saved_objects/{type}', () => { expect(savedObjectsClient.create.calledOnce).toBe(true); const args = savedObjectsClient.create.getCall(0).args; - const options = { overwrite: false, id: 'logstash-*' }; + const options = { overwrite: false, id: 'logstash-*', references: [] }; const attributes = { title: 'Testing' }; expect(args).toEqual(['index-pattern', attributes, options]); diff --git a/src/server/saved_objects/routes/find.js b/src/server/saved_objects/routes/find.js index 27a30f0aa6427..2dd92c01e9867 100644 --- a/src/server/saved_objects/routes/find.js +++ b/src/server/saved_objects/routes/find.js @@ -34,6 +34,11 @@ export const createFindRoute = (prereqs) => ({ default_search_operator: Joi.string().valid('OR', 'AND').default('OR'), search_fields: Joi.array().items(Joi.string()).single(), sort_field: Joi.array().items(Joi.string()).single(), + has_reference: Joi.object() + .keys({ + type: Joi.string().required(), + id: Joi.string().required(), + }).optional(), fields: Joi.array().items(Joi.string()).single() }).default() }, diff --git a/src/server/saved_objects/routes/find.test.js b/src/server/saved_objects/routes/find.test.js index 5c865c22f426b..d11ac8f00cb54 100644 --- a/src/server/saved_objects/routes/find.test.js +++ b/src/server/saved_objects/routes/find.test.js @@ -74,13 +74,15 @@ describe('GET /api/saved_objects/_find', () => { id: 'logstash-*', title: 'logstash-*', timeFieldName: '@timestamp', - notExpandable: true + notExpandable: true, + references: [], }, { type: 'index-pattern', id: 'stocks-*', title: 'stocks-*', timeFieldName: '@timestamp', - notExpandable: true + notExpandable: true, + references: [], } ] }; diff --git a/src/server/saved_objects/routes/get.test.js b/src/server/saved_objects/routes/get.test.js index 63246bcf3a329..52ccb38601fb7 100644 --- a/src/server/saved_objects/routes/get.test.js +++ b/src/server/saved_objects/routes/get.test.js @@ -53,7 +53,8 @@ describe('GET /api/saved_objects/{type}/{id}', () => { id: 'logstash-*', title: 'logstash-*', timeFieldName: '@timestamp', - notExpandable: true + notExpandable: true, + references: [], }; savedObjectsClient.get.returns(Promise.resolve(clientResponse)); diff --git a/src/server/saved_objects/routes/update.js b/src/server/saved_objects/routes/update.js index cc7e2c0a4b4d1..163fb77d5b62f 100644 --- a/src/server/saved_objects/routes/update.js +++ b/src/server/saved_objects/routes/update.js @@ -32,14 +32,22 @@ export const createUpdateRoute = (prereqs) => { }).required(), payload: Joi.object({ attributes: Joi.object().required(), - version: Joi.number().min(1) + version: Joi.number().min(1), + references: Joi.array().items( + Joi.object() + .keys({ + name: Joi.string().required(), + type: Joi.string().required(), + id: Joi.string().required(), + }), + ).default([]), }).required() }, handler(request) { const { savedObjectsClient } = request.pre; const { type, id } = request.params; - const { attributes, version } = request.payload; - const options = { version }; + const { attributes, version, references } = request.payload; + const options = { version, references }; return savedObjectsClient.update(type, id, attributes, options); } diff --git a/src/server/saved_objects/routes/update.test.js b/src/server/saved_objects/routes/update.test.js index ac4d938794fda..72a540979c2de 100644 --- a/src/server/saved_objects/routes/update.test.js +++ b/src/server/saved_objects/routes/update.test.js @@ -51,7 +51,8 @@ describe('PUT /api/saved_objects/{type}/{id?}', () => { payload: { attributes: { title: 'Testing' - } + }, + references: [], } }; @@ -66,7 +67,7 @@ describe('PUT /api/saved_objects/{type}/{id?}', () => { it('calls upon savedObjectClient.update', async () => { const attributes = { title: 'Testing' }; - const options = { version: 2 }; + const options = { version: 2, references: [] }; const request = { method: 'PUT', url: '/api/saved_objects/index-pattern/logstash-*', diff --git a/src/server/saved_objects/serialization/index.ts b/src/server/saved_objects/serialization/index.ts index 09b8ddd94325a..94807ddeb7668 100644 --- a/src/server/saved_objects/serialization/index.ts +++ b/src/server/saved_objects/serialization/index.ts @@ -43,13 +43,22 @@ export interface MigrationVersion { [type: string]: string; } +/** + * A reference object to anohter saved object. + */ +export interface SavedObjectReference { + name: string; + type: string; + id: string; +} + /** * A saved object type definition that allows for miscellaneous, unknown * properties, as current discussions around security, ACLs, etc indicate * that future props are likely to be added. Migrations support this * scenario out of the box. */ -export interface SavedObjectDoc { +interface SavedObjectDoc { attributes: object; id: string; type: string; @@ -61,6 +70,19 @@ export interface SavedObjectDoc { [rootProp: string]: any; } +interface Referencable { + references: SavedObjectReference[]; +} + +/** + * We want to have two types, one that guarantees a "references" attribute + * will exist and one that allows it to be null. Since we're not migrating + * all the saved objects to have a "references" array, we need to support + * the scenarios where it may be missing (ex migrations). + */ +export type RawSavedObjectDoc = SavedObjectDoc & Partial; +export type SanitizedSavedObjectDoc = SavedObjectDoc & Referencable; + function assertNonEmptyString(value: string, name: string) { if (!value || typeof value !== 'string') { throw new TypeError(`Expected "${value}" to be a ${name}`); @@ -94,13 +116,14 @@ export class SavedObjectsSerializer { * * @param {RawDoc} rawDoc - The raw ES document to be converted to saved object format. */ - public rawToSavedObject({ _id, _source, _version }: RawDoc): SavedObjectDoc { + public rawToSavedObject({ _id, _source, _version }: RawDoc): SanitizedSavedObjectDoc { const { type, namespace } = _source; return { type, id: this.trimIdPrefix(namespace, type, _id), ...(namespace && !this.schema.isNamespaceAgnostic(type) && { namespace }), attributes: _source[type], + references: _source.references || [], ...(_source.migrationVersion && { migrationVersion: _source.migrationVersion }), ...(_source.updated_at && { updated_at: _source.updated_at }), ...(_version != null && { version: _version }), @@ -110,13 +133,23 @@ export class SavedObjectsSerializer { /** * Converts a document from the saved object client format to the format that is stored in elasticsearch. * - * @param {SavedObjectDoc} savedObj - The saved object to be converted to raw ES format. + * @param {SanitizedSavedObjectDoc} savedObj - The saved object to be converted to raw ES format. */ - public savedObjectToRaw(savedObj: SavedObjectDoc): RawDoc { - const { id, type, namespace, attributes, migrationVersion, updated_at, version } = savedObj; + public savedObjectToRaw(savedObj: SanitizedSavedObjectDoc): RawDoc { + const { + id, + type, + namespace, + attributes, + migrationVersion, + updated_at, + version, + references, + } = savedObj; const source = { [type]: attributes, type, + references, ...(namespace && !this.schema.isNamespaceAgnostic(type) && { namespace }), ...(migrationVersion && { migrationVersion }), ...(updated_at && { updated_at }), diff --git a/src/server/saved_objects/serialization/serialization.test.ts b/src/server/saved_objects/serialization/serialization.test.ts index 43ff4a2df7252..2178105fc289e 100644 --- a/src/server/saved_objects/serialization/serialization.test.ts +++ b/src/server/saved_objects/serialization/serialization.test.ts @@ -34,6 +34,24 @@ describe('saved object conversion', () => { expect(actual).toHaveProperty('type', 'foo'); }); + test('it copies the _source.references property to references', () => { + const serializer = new SavedObjectsSerializer(new SavedObjectsSchema()); + const actual = serializer.rawToSavedObject({ + _id: 'foo:bar', + _source: { + type: 'foo', + references: [{ name: 'ref_0', type: 'index-pattern', id: 'pattern*' }], + }, + }); + expect(actual).toHaveProperty('references', [ + { + name: 'ref_0', + type: 'index-pattern', + id: 'pattern*', + }, + ]); + }); + test('if specified it copies the _source.migrationVersion property to migrationVersion', () => { const serializer = new SavedObjectsSerializer(new SavedObjectsSchema()); const actual = serializer.rawToSavedObject({ @@ -95,6 +113,7 @@ describe('saved object conversion', () => { acl: '33.3.5', }, updated_at: now, + references: [], }; expect(expected).toEqual(actual); }); @@ -166,6 +185,7 @@ describe('saved object conversion', () => { attributes: { world: 'earth', }, + references: [], }); }); @@ -180,6 +200,7 @@ describe('saved object conversion', () => { expect(actual).toEqual({ id: 'universe', type: 'hello', + references: [], }); }); @@ -214,6 +235,7 @@ describe('saved object conversion', () => { }, namespace: 'foo-namespace', updated_at: new Date(), + references: [], }, }; @@ -385,6 +407,23 @@ describe('saved object conversion', () => { expect(actual._source).toHaveProperty('type', 'foo'); }); + test('it copies the references property to _source.references', () => { + const serializer = new SavedObjectsSerializer(new SavedObjectsSchema()); + const actual = serializer.savedObjectToRaw({ + id: '1', + type: 'foo', + attributes: {}, + references: [{ name: 'ref_0', type: 'index-pattern', id: 'pattern*' }], + }); + expect(actual._source).toHaveProperty('references', [ + { + name: 'ref_0', + type: 'index-pattern', + id: 'pattern*', + }, + ]); + }); + test('if specified it copies the updated_at property to _source.updated_at', () => { const serializer = new SavedObjectsSerializer(new SavedObjectsSchema()); const now = new Date(); diff --git a/src/server/saved_objects/service/lib/repository.js b/src/server/saved_objects/service/lib/repository.js index c047b6d72edc1..01b79b4050a55 100644 --- a/src/server/saved_objects/service/lib/repository.js +++ b/src/server/saved_objects/service/lib/repository.js @@ -72,6 +72,7 @@ export class SavedObjectsRepository { * @property {boolean} [options.overwrite=false] * @property {object} [options.migrationVersion=undefined] * @property {string} [options.namespace] + * @property {array} [options.references] - [{ name, type, id }] * @returns {promise} - { id, type, version, attributes } */ async create(type, attributes = {}, options = {}) { @@ -80,6 +81,7 @@ export class SavedObjectsRepository { migrationVersion, overwrite = false, namespace, + references = [], } = options; const method = id && !overwrite ? 'create' : 'index'; @@ -93,6 +95,7 @@ export class SavedObjectsRepository { attributes, migrationVersion, updated_at: time, + references, }); const raw = this._serializer.savedObjectToRaw(migrated); @@ -122,11 +125,11 @@ export class SavedObjectsRepository { /** * Creates multiple documents at once * - * @param {array} objects - [{ type, id, attributes, migrationVersion }] + * @param {array} objects - [{ type, id, attributes, references, migrationVersion }] * @param {object} [options={}] * @property {boolean} [options.overwrite=false] - overwrites existing documents * @property {string} [options.namespace] - * @returns {promise} - {saved_objects: [[{ id, type, version, attributes, error: { message } }]} + * @returns {promise} - {saved_objects: [[{ id, type, version, references, attributes, error: { message } }]} */ async bulkCreate(objects, options = {}) { const { @@ -143,6 +146,7 @@ export class SavedObjectsRepository { migrationVersion: object.migrationVersion, namespace, updated_at: time, + references: object.references || [], }); const raw = this._serializer.savedObjectToRaw(migrated); @@ -178,6 +182,7 @@ export class SavedObjectsRepository { id = responseId, type, attributes, + references = [], } = objects[i]; if (error) { @@ -202,7 +207,8 @@ export class SavedObjectsRepository { type, updated_at: time, version, - attributes + attributes, + references, }; }) }; @@ -292,6 +298,7 @@ export class SavedObjectsRepository { * @property {string} [options.sortOrder] * @property {Array} [options.fields] * @property {string} [options.namespace] + * @property {object} [options.hasReference] - { type, id } * @returns {promise} - { saved_objects: [{ id, type, version, attributes }], total, per_page, page } */ async find(options = {}) { @@ -300,6 +307,7 @@ export class SavedObjectsRepository { search, defaultSearchOperator = 'OR', searchFields, + hasReference, page = 1, perPage = 20, sortField, @@ -337,6 +345,7 @@ export class SavedObjectsRepository { sortField, sortOrder, namespace, + hasReference, }) } }; @@ -414,6 +423,7 @@ export class SavedObjectsRepository { ...time && { updated_at: time }, version: doc._version, attributes: doc._source[type], + references: doc._source.references || [], migrationVersion: doc._source.migrationVersion, }; }) @@ -456,6 +466,7 @@ export class SavedObjectsRepository { ...updatedAt && { updated_at: updatedAt }, version: response._version, attributes: response._source[type], + references: response._source.references || [], migrationVersion: response._source.migrationVersion, }; } @@ -468,12 +479,14 @@ export class SavedObjectsRepository { * @param {object} [options={}] * @property {integer} options.version - ensures version matches that of persisted object * @property {string} [options.namespace] + * @property {array} [options.references] - [{ name, type, id }] * @returns {promise} */ async update(type, id, attributes, options = {}) { const { version, - namespace + namespace, + references = [], } = options; const time = this._getCurrentTime(); @@ -488,6 +501,7 @@ export class SavedObjectsRepository { doc: { [type]: attributes, updated_at: time, + references, } }, }); @@ -502,6 +516,7 @@ export class SavedObjectsRepository { type, updated_at: time, version: response._version, + references, attributes }; } @@ -575,6 +590,7 @@ export class SavedObjectsRepository { id, type, updated_at: time, + references: response.get._source.references, version: response._version, attributes: response.get._source[type], }; diff --git a/src/server/saved_objects/service/lib/repository.test.js b/src/server/saved_objects/service/lib/repository.test.js index 2862785694211..c5e1d609d2d58 100644 --- a/src/server/saved_objects/service/lib/repository.test.js +++ b/src/server/saved_objects/service/lib/repository.test.js @@ -259,6 +259,11 @@ describe('SavedObjectsRepository', () => { }, { id: 'logstash-*', namespace: 'foo-namespace', + references: [{ + name: 'ref_0', + type: 'test', + id: '123', + }], }); expect(response).toEqual({ @@ -268,7 +273,12 @@ describe('SavedObjectsRepository', () => { version: 2, attributes: { title: 'Logstash', - } + }, + references: [{ + name: 'ref_0', + type: 'test', + id: '123', + }], }); }); @@ -428,8 +438,8 @@ describe('SavedObjectsRepository', () => { callAdminCluster.returns({ items: [] }); await savedObjectsRepository.bulkCreate([ - { type: 'config', id: 'one', attributes: { title: 'Test One' } }, - { type: 'index-pattern', id: 'two', attributes: { title: 'Test Two' } } + { type: 'config', id: 'one', attributes: { title: 'Test One' }, references: [{ name: 'ref_0', type: 'test', id: '1' }] }, + { type: 'index-pattern', id: 'two', attributes: { title: 'Test Two' }, references: [{ name: 'ref_0', type: 'test', id: '2' }] }, ]); sinon.assert.calledOnce(callAdminCluster); @@ -439,9 +449,14 @@ describe('SavedObjectsRepository', () => { expect(bulkCalls[0][1].body).toEqual([ { create: { _type: '_doc', _id: 'config:one' } }, - { type: 'config', ...mockTimestampFields, config: { title: 'Test One' } }, + { type: 'config', ...mockTimestampFields, config: { title: 'Test One' }, references: [{ name: 'ref_0', type: 'test', id: '1' }] }, { create: { _type: '_doc', _id: 'index-pattern:two' } }, - { type: 'index-pattern', ...mockTimestampFields, 'index-pattern': { title: 'Test Two' } } + { + type: 'index-pattern', + ...mockTimestampFields, + 'index-pattern': { title: 'Test Two' }, + references: [{ name: 'ref_0', type: 'test', id: '2' }], + }, ]); sinon.assert.calledOnce(onBeforeWrite); @@ -463,9 +478,21 @@ describe('SavedObjectsRepository', () => { sinon.assert.calledWithExactly(callAdminCluster, 'bulk', sinon.match({ body: [ { create: { _type: '_doc', _id: 'config:one' } }, - { type: 'config', ...mockTimestampFields, config: { title: 'Test One!!' }, migrationVersion: { foo: '2.3.4' } }, + { + type: 'config', + ...mockTimestampFields, + config: { title: 'Test One!!' }, + migrationVersion: { foo: '2.3.4' }, + references: [], + }, { create: { _type: '_doc', _id: 'index-pattern:two' } }, - { type: 'index-pattern', ...mockTimestampFields, 'index-pattern': { title: 'Test Two!!' }, migrationVersion: { foo: '2.3.4' } } + { + type: 'index-pattern', + ...mockTimestampFields, + 'index-pattern': { title: 'Test Two!!' }, + migrationVersion: { foo: '2.3.4' }, + references: [], + }, ] })); }); @@ -479,7 +506,7 @@ describe('SavedObjectsRepository', () => { body: [ // uses create because overwriting is not allowed { create: { _type: '_doc', _id: 'foo:bar' } }, - { type: 'foo', ...mockTimestampFields, 'foo': {} }, + { type: 'foo', ...mockTimestampFields, 'foo': {}, references: [] }, ] })); @@ -494,7 +521,7 @@ describe('SavedObjectsRepository', () => { body: [ // uses index because overwriting is allowed { index: { _type: '_doc', _id: 'foo:bar' } }, - { type: 'foo', ...mockTimestampFields, 'foo': {} }, + { type: 'foo', ...mockTimestampFields, 'foo': {}, references: [] }, ] })); @@ -538,6 +565,7 @@ describe('SavedObjectsRepository', () => { version: 2, ...mockTimestampFields, attributes: { title: 'Test Two' }, + references: [], } ] }); @@ -576,12 +604,14 @@ describe('SavedObjectsRepository', () => { version: 2, ...mockTimestampFields, attributes: { title: 'Test One' }, + references: [], }, { id: 'two', type: 'index-pattern', version: 2, ...mockTimestampFields, attributes: { title: 'Test Two' }, + references: [], } ] }); @@ -602,9 +632,21 @@ describe('SavedObjectsRepository', () => { sinon.assert.calledWithExactly(callAdminCluster, 'bulk', sinon.match({ body: [ { create: { _type: '_doc', _id: 'foo-namespace:config:one' } }, - { namespace: 'foo-namespace', type: 'config', ...mockTimestampFields, config: { title: 'Test One' } }, + { + namespace: 'foo-namespace', + type: 'config', + ...mockTimestampFields, + config: { title: 'Test One' }, + references: [], + }, { create: { _type: '_doc', _id: 'foo-namespace:index-pattern:two' } }, - { namespace: 'foo-namespace', type: 'index-pattern', ...mockTimestampFields, 'index-pattern': { title: 'Test Two' } } + { + namespace: 'foo-namespace', + type: 'index-pattern', + ...mockTimestampFields, + 'index-pattern': { title: 'Test Two' }, + references: [], + }, ] })); sinon.assert.calledOnce(onBeforeWrite); @@ -620,9 +662,9 @@ describe('SavedObjectsRepository', () => { sinon.assert.calledWithExactly(callAdminCluster, 'bulk', sinon.match({ body: [ { create: { _type: '_doc', _id: 'config:one' } }, - { type: 'config', ...mockTimestampFields, config: { title: 'Test One' } }, + { type: 'config', ...mockTimestampFields, config: { title: 'Test One' }, references: [] }, { create: { _type: '_doc', _id: 'index-pattern:two' } }, - { type: 'index-pattern', ...mockTimestampFields, 'index-pattern': { title: 'Test Two' } } + { type: 'index-pattern', ...mockTimestampFields, 'index-pattern': { title: 'Test Two' }, references: [] } ] })); sinon.assert.calledOnce(onBeforeWrite); @@ -642,7 +684,7 @@ describe('SavedObjectsRepository', () => { sinon.assert.calledWithExactly(callAdminCluster, 'bulk', sinon.match({ body: [ { create: { _type: '_doc', _id: 'globaltype:one' } }, - { type: 'globaltype', ...mockTimestampFields, 'globaltype': { title: 'Test One' } }, + { type: 'globaltype', ...mockTimestampFields, 'globaltype': { title: 'Test One' }, references: [] }, ] })); sinon.assert.calledOnce(onBeforeWrite); @@ -812,22 +854,29 @@ describe('SavedObjectsRepository', () => { } }); - it('passes mappings, schema, search, defaultSearchOperator, searchFields, type, sortField, and sortOrder to getSearchDsl', async () => { - callAdminCluster.returns(namespacedSearchResults); - const relevantOpts = { - namespace: 'foo-namespace', - search: 'foo*', - searchFields: ['foo'], - type: 'bar', - sortField: 'name', - sortOrder: 'desc', - defaultSearchOperator: 'AND', - }; + it( + 'passes mappings, schema, search, defaultSearchOperator, searchFields, type, sortField, sortOrder and hasReference to getSearchDsl', + async () => { + callAdminCluster.returns(namespacedSearchResults); + const relevantOpts = { + namespace: 'foo-namespace', + search: 'foo*', + searchFields: ['foo'], + type: 'bar', + sortField: 'name', + sortOrder: 'desc', + defaultSearchOperator: 'AND', + hasReference: { + type: 'foo', + id: '1', + }, + }; - await savedObjectsRepository.find(relevantOpts); - sinon.assert.calledOnce(getSearchDsl); - sinon.assert.calledWithExactly(getSearchDsl, mappings, schema, relevantOpts); - }); + await savedObjectsRepository.find(relevantOpts); + sinon.assert.calledOnce(getSearchDsl); + sinon.assert.calledWithExactly(getSearchDsl, mappings, schema, relevantOpts); + } + ); it('merges output of getSearchDsl into es request body', async () => { callAdminCluster.returns(noNamespaceSearchResults); @@ -858,7 +907,8 @@ describe('SavedObjectsRepository', () => { type: doc._source.type, ...mockTimestampFields, version: doc._version, - attributes: doc._source[doc._source.type] + attributes: doc._source[doc._source.type], + references: [], }); }); }); @@ -881,7 +931,8 @@ describe('SavedObjectsRepository', () => { type: doc._source.type, ...mockTimestampFields, version: doc._version, - attributes: doc._source[doc._source.type] + attributes: doc._source[doc._source.type], + references: [], }); }); }); @@ -970,7 +1021,8 @@ describe('SavedObjectsRepository', () => { version: 2, attributes: { title: 'Testing' - } + }, + references: [], }); }); @@ -985,7 +1037,8 @@ describe('SavedObjectsRepository', () => { version: 2, attributes: { title: 'Testing' - } + }, + references: [], }); }); @@ -1132,7 +1185,8 @@ describe('SavedObjectsRepository', () => { type: 'config', ...mockTimestampFields, version: 2, - attributes: { title: 'Test' } + attributes: { title: 'Test' }, + references: [], }); expect(savedObjects[1]).toEqual({ id: 'bad', @@ -1168,13 +1222,25 @@ describe('SavedObjectsRepository', () => { }); it('returns current ES document version', async () => { - const response = await savedObjectsRepository.update('index-pattern', 'logstash-*', attributes, { namespace: 'foo-namespace' }); + const response = await savedObjectsRepository.update('index-pattern', 'logstash-*', attributes, { + namespace: 'foo-namespace', + references: [{ + name: 'ref_0', + type: 'test', + id: '1', + }], + }); expect(response).toEqual({ id, type, ...mockTimestampFields, version: newVersion, - attributes + attributes, + references: [{ + name: 'ref_0', + type: 'test', + id: '1', + }], }); }); @@ -1197,6 +1263,11 @@ describe('SavedObjectsRepository', () => { title: 'Testing', }, { namespace: 'foo-namespace', + references: [{ + name: 'ref_0', + type: 'test', + id: '1', + }], }); sinon.assert.calledOnce(callAdminCluster); @@ -1205,7 +1276,15 @@ describe('SavedObjectsRepository', () => { id: 'foo-namespace:index-pattern:logstash-*', version: undefined, body: { - doc: { updated_at: mockTimestamp, 'index-pattern': { title: 'Testing' } } + doc: { + updated_at: mockTimestamp, + 'index-pattern': { title: 'Testing' }, + references: [{ + name: 'ref_0', + type: 'test', + id: '1', + }], + }, }, ignore: [404], refresh: 'wait_for', @@ -1216,7 +1295,15 @@ describe('SavedObjectsRepository', () => { }); it(`doesn't prepend namespace to the id or add namespace property when providing no namespace for namespaced type`, async () => { - await savedObjectsRepository.update('index-pattern', 'logstash-*', { title: 'Testing' }); + await savedObjectsRepository.update('index-pattern', 'logstash-*', { + title: 'Testing', + }, { + references: [{ + name: 'ref_0', + type: 'test', + id: '1', + }], + }); sinon.assert.calledOnce(callAdminCluster); sinon.assert.calledWithExactly(callAdminCluster, 'update', { @@ -1224,7 +1311,15 @@ describe('SavedObjectsRepository', () => { id: 'index-pattern:logstash-*', version: undefined, body: { - doc: { updated_at: mockTimestamp, 'index-pattern': { title: 'Testing' } } + doc: { + updated_at: mockTimestamp, + 'index-pattern': { title: 'Testing' }, + references: [{ + name: 'ref_0', + type: 'test', + id: '1', + }], + }, }, ignore: [404], refresh: 'wait_for', @@ -1239,6 +1334,11 @@ describe('SavedObjectsRepository', () => { name: 'bar', }, { namespace: 'foo-namespace', + references: [{ + name: 'ref_0', + type: 'test', + id: '1', + }], }); sinon.assert.calledOnce(callAdminCluster); @@ -1247,7 +1347,15 @@ describe('SavedObjectsRepository', () => { id: 'globaltype:foo', version: undefined, body: { - doc: { updated_at: mockTimestamp, 'globaltype': { name: 'bar' } } + doc: { + updated_at: mockTimestamp, + 'globaltype': { name: 'bar' }, + references: [{ + name: 'ref_0', + type: 'test', + id: '1', + }], + }, }, ignore: [404], refresh: 'wait_for', diff --git a/src/server/saved_objects/service/lib/search_dsl/query_params.js b/src/server/saved_objects/service/lib/search_dsl/query_params.js index 47e5812e5eb2e..cd316593dde9e 100644 --- a/src/server/saved_objects/service/lib/search_dsl/query_params.js +++ b/src/server/saved_objects/service/lib/search_dsl/query_params.js @@ -99,13 +99,37 @@ function getClauseForType(schema, namespace, type) { * @param {String} search * @param {Array} searchFields * @param {String} defaultSearchOperator + * @param {Object} hasReference * @return {Object} */ -export function getQueryParams(mappings, schema, namespace, type, search, searchFields, defaultSearchOperator) { +export function getQueryParams(mappings, schema, namespace, type, search, searchFields, defaultSearchOperator, hasReference) { const types = getTypes(mappings, type); const bool = { filter: [{ bool: { + must: hasReference + ? [{ + nested: { + path: 'references', + query: { + bool: { + must: [ + { + term: { + 'references.id': hasReference.id, + }, + }, + { + term: { + 'references.type': hasReference.type, + }, + }, + ], + }, + }, + }, + }] + : undefined, should: types.map(type => getClauseForType(schema, namespace, type)), minimum_should_match: 1 } diff --git a/src/server/saved_objects/service/lib/search_dsl/query_params.test.js b/src/server/saved_objects/service/lib/search_dsl/query_params.test.js index 8e98fe3a023cb..1d0a8063be992 100644 --- a/src/server/saved_objects/service/lib/search_dsl/query_params.test.js +++ b/src/server/saved_objects/service/lib/search_dsl/query_params.test.js @@ -778,4 +778,46 @@ describe('searchDsl/queryParams', () => { }); }); }); + + describe('type (plural, namespaced and global), hasReference', () => { + it('supports hasReference', () => { + expect(getQueryParams(MAPPINGS, SCHEMA, 'foo-namespace', ['saved', 'global'], null, null, 'OR', { type: 'bar', id: '1' })) + .toEqual({ + query: { + bool: { + filter: [{ + bool: { + must: [{ + nested: { + path: 'references', + query: { + bool: { + must: [ + { + term: { + 'references.id': '1', + }, + }, + { + term: { + 'references.type': 'bar', + }, + }, + ], + }, + }, + }, + }], + should: [ + createTypeClause('saved', 'foo-namespace'), + createTypeClause('global'), + ], + minimum_should_match: 1, + } + }] + } + } + }); + }); + }); }); diff --git a/src/server/saved_objects/service/lib/search_dsl/search_dsl.js b/src/server/saved_objects/service/lib/search_dsl/search_dsl.js index d6a224f4c3857..3a11cd0c8e91d 100644 --- a/src/server/saved_objects/service/lib/search_dsl/search_dsl.js +++ b/src/server/saved_objects/service/lib/search_dsl/search_dsl.js @@ -31,6 +31,7 @@ export function getSearchDsl(mappings, schema, options = {}) { sortField, sortOrder, namespace, + hasReference, } = options; if (!type) { @@ -42,7 +43,7 @@ export function getSearchDsl(mappings, schema, options = {}) { } return { - ...getQueryParams(mappings, schema, namespace, type, search, searchFields, defaultSearchOperator), + ...getQueryParams(mappings, schema, namespace, type, search, searchFields, defaultSearchOperator, hasReference), ...getSortingParams(mappings, type, sortField, sortOrder), }; } diff --git a/src/server/saved_objects/service/lib/search_dsl/search_dsl.test.js b/src/server/saved_objects/service/lib/search_dsl/search_dsl.test.js index 1ba780fc79ed0..0600c01848346 100644 --- a/src/server/saved_objects/service/lib/search_dsl/search_dsl.test.js +++ b/src/server/saved_objects/service/lib/search_dsl/search_dsl.test.js @@ -46,7 +46,7 @@ describe('getSearchDsl', () => { }); describe('passes control', () => { - it('passes (mappings, schema, namespace, type, search, searchFields) to getQueryParams', () => { + it('passes (mappings, schema, namespace, type, search, searchFields, hasReference) to getQueryParams', () => { const spy = sandbox.spy(queryParamsNS, 'getQueryParams'); const mappings = { type: { properties: {} } }; const schema = { isNamespaceAgnostic: () => {} }; @@ -56,6 +56,10 @@ describe('getSearchDsl', () => { search: 'bar', searchFields: ['baz'], defaultSearchOperator: 'AND', + hasReference: { + type: 'bar', + id: '1', + }, }; getSearchDsl(mappings, schema, opts); @@ -69,6 +73,7 @@ describe('getSearchDsl', () => { opts.search, opts.searchFields, opts.defaultSearchOperator, + opts.hasReference, ); }); diff --git a/src/server/saved_objects/service/saved_objects_client.d.ts b/src/server/saved_objects/service/saved_objects_client.d.ts index a6e10aa1b85b1..a58cec023b335 100644 --- a/src/server/saved_objects/service/saved_objects_client.d.ts +++ b/src/server/saved_objects/service/saved_objects_client.d.ts @@ -36,7 +36,7 @@ export interface BulkCreateObject { } export interface BulkCreateResponse { - savedObjects: Array>; + saved_objects: Array>; } export interface FindOptions extends BaseOptions { @@ -68,7 +68,7 @@ export interface BulkGetObject { export type BulkGetObjects = BulkGetObject[]; export interface BulkGetResponse { - savedObjects: Array>; + saved_objects: Array>; } export interface SavedObjectAttributes { @@ -84,6 +84,13 @@ export interface SavedObject { message: string; }; attributes: T; + references: SavedObjectReference[]; +} + +export interface SavedObjectReference { + name: string; + type: string; + id: string; } export declare class SavedObjectsClient { diff --git a/src/server/saved_objects/service/saved_objects_client.js b/src/server/saved_objects/service/saved_objects_client.js index a354067e6f702..a18881ad09d41 100644 --- a/src/server/saved_objects/service/saved_objects_client.js +++ b/src/server/saved_objects/service/saved_objects_client.js @@ -148,6 +148,7 @@ export class SavedObjectsClient { * @property {string} [options.sortOrder] * @property {Array} [options.fields] * @property {string} [options.namespace] + * @property {object} [options.hasReference] - { type, id } * @returns {promise} - { saved_objects: [{ id, type, version, attributes }], total, per_page, page } */ async find(options = {}) { diff --git a/src/ui/public/agg_types/buckets/terms.js b/src/ui/public/agg_types/buckets/terms.js index 55e0625cec694..28bf458d03d32 100644 --- a/src/ui/public/agg_types/buckets/terms.js +++ b/src/ui/public/agg_types/buckets/terms.js @@ -108,10 +108,15 @@ export const termsBucketAgg = new BucketAggType({ const filterAgg = buildOtherBucketAgg(aggConfigs, aggConfig, resp); nestedSearchSource.setField('aggs', filterAgg); - const request = inspectorAdapters.requests.start('Other bucket', { - description: `This request counts the number of documents that fall - outside the criterion of the data buckets.` - }); + const request = inspectorAdapters.requests.start( + i18n.translate('common.ui.aggTypes.buckets.terms.otherBucketTitle', { defaultMessage: 'Other bucket' }), + { + description: i18n.translate('common.ui.aggTypes.buckets.terms.otherBucketDescription', { + defaultMessage: 'This request counts the number of documents that fall ' + + 'outside the criterion of the data buckets.' + }), + } + ); nestedSearchSource.getSearchRequestBody().then(body => { request.json(body); }); @@ -161,7 +166,9 @@ export const termsBucketAgg = new BucketAggType({ try { return agg.makeLabel(); } catch (e) { - return '- agg not valid -'; + return i18n.translate('common.ui.aggTypes.buckets.terms.aggNotValidLabel', { + defaultMessage: '- agg not valid -', + }); } }; diff --git a/src/ui/public/agg_types/controls/ranges.html b/src/ui/public/agg_types/controls/ranges.html index 4255c671628e1..e24b6f4378857 100644 --- a/src/ui/public/agg_types/controls/ranges.html +++ b/src/ui/public/agg_types/controls/ranges.html @@ -39,7 +39,7 @@ + + i18n-id="common.ui.filterBar.cancelButtonLabel" + i18n-default-message="Cancel" + >
@@ -50,7 +69,10 @@ class="euiLink euiLink--primary" data-test-subj="addFilter" > - Add a filter + @@ -68,7 +90,10 @@ aria-expanded="{{!!showFilterActions}}" aria-controls="filterActionsAllContainer" > - Actions + @@ -121,28 +146,83 @@ >
diff --git a/src/ui/public/filter_bar/filter_bar.js b/src/ui/public/filter_bar/filter_bar.js index 0a1648ac75d4a..88cc6146706a7 100644 --- a/src/ui/public/filter_bar/filter_bar.js +++ b/src/ui/public/filter_bar/filter_bar.js @@ -37,7 +37,7 @@ export { disableFilter, enableFilter, toggleFilterDisabled } from './lib/disable const module = uiModules.get('kibana'); -module.directive('filterBar', function (Private, Promise, getAppState) { +module.directive('filterBar', function (Private, Promise, getAppState, i18n) { const mapAndFlattenFilters = Private(FilterBarLibMapAndFlattenFiltersProvider); const mapFlattenAndWrapFilters = Private(FilterBarLibMapFlattenAndWrapFiltersProvider); const extractTimeFilter = Private(FilterBarLibExtractTimeFilterProvider); @@ -74,20 +74,25 @@ module.directive('filterBar', function (Private, Promise, getAppState) { return pill[pill.length - 1].offsetTop > 10; }; + const collapseFilterTooltip = i18n('common.ui.filterBar.collapseFilterTooltip', { + defaultMessage: 'Collapse filter bar \n to show less' + }); + const expandFilterTooltip = i18n('common.ui.filterBar.expandFilterTooltip', { defaultMessage: 'Expand filter bar \n to show more' }); + $scope.filterNavToggle = { isOpen: true, - tooltipContent: 'Collapse filter bar \n to show less' + tooltipContent: collapseFilterTooltip }; $scope.toggleFilterShown = () => { const collapser = $elem.find('.filter-nav-link__collapser'); const filterPanelPill = $elem.find('.filter-panel__pill'); if ($scope.filterNavToggle.isOpen) { - $scope.filterNavToggle.tooltipContent = 'Expand filter bar \n to show more'; + $scope.filterNavToggle.tooltipContent = expandFilterTooltip; collapser.attr('aria-expanded', 'false'); filterPanelPill.attr('style', 'width: calc(100% - 80px)'); } else { - $scope.filterNavToggle.tooltipContent = 'Collapse filter bar \n to show less'; + $scope.filterNavToggle.tooltipContent = collapseFilterTooltip; collapser.attr('aria-expanded', 'true'); filterPanelPill.attr('style', 'width: auto'); } diff --git a/src/ui/public/filter_bar/filter_pill/filter_pill.html b/src/ui/public/filter_bar/filter_pill/filter_pill.html index f79d42665e8fc..f1cdadad492f6 100644 --- a/src/ui/public/filter_bar/filter_pill/filter_pill.html +++ b/src/ui/public/filter_bar/filter_pill/filter_pill.html @@ -13,7 +13,11 @@ aria-disabled="{{pill.filter.meta.disabled}}" > - NOT + {{ pill.filter.meta.alias }} {{ pill.filter.meta.key }}: "{{ pill.filter.meta.value }}" @@ -26,8 +30,8 @@ data-test-subj="disableFilter-{{ pill.filter.meta.key }}" ng-focus="pill.activateActions()" ng-blur="pill.deactivateActions()" - aria-label="{{pill.filter.meta.disabled ? 'Enable filter' : 'Disable filter'}}" - tooltip="{{pill.filter.meta.disabled ? 'Enable filter' : 'Disable filter'}}" + aria-label="{{pill.filter.meta.disabled ? pill.i18n.enableFilterAriaLabel : pill.i18n.disableFilterAriaLabel}}" + tooltip="{{pill.filter.meta.disabled ? pill.i18n.enableFilterTooltip : pill.i18n.disableFilterTooltip}}" tooltip-append-to-body="true" > @@ -40,8 +44,8 @@ data-test-subj="pinFilter-{{ pill.filter.meta.key }}" ng-focus="pill.activateActions()" ng-blur="pill.deactivateActions()" - aria-label="{{pill.filter.$state.store == 'globalState' ? 'Unpin filter' : 'Pin filter'}}" - tooltip="{{pill.filter.$state.store == 'globalState' ? 'Unpin filter' : 'Pin filter'}}" + aria-label="{{pill.filter.$state.store == 'globalState' ? pill.i18n.unpinFilterAriaLabel : pill.i18n.pinFilterAriaLabel}}" + tooltip="{{pill.filter.$state.store == 'globalState' ? pill.i18n.unpinFilterTooltip : pill.i18n.pinFilterTooltip}}" tooltip-append-to-body="true" > @@ -54,8 +58,8 @@ data-test-subj="invertFilter-{{ pill.filter.meta.key }}" ng-focus="pill.activateActions()" ng-blur="pill.deactivateActions()" - aria-label="{{pill.filter.meta.negate ? 'Include matches' : 'Exclude matches'}}" - tooltip="{{pill.filter.meta.negate ? 'Include matches' : 'Exclude matches'}}" + aria-label="{{pill.filter.meta.negate ? pill.i18n.includeMatchesAriaLabel : pill.i18n.excludeMatchesAriaLabel}}" + tooltip="{{pill.filter.meta.negate ? pill.i18n.includeMatchesTooltip : pill.i18n.excludeMatchesTooltip}}" tooltip-append-to-body="true" > @@ -67,8 +71,8 @@ ng-click="pill.onDeleteFilter(pill.filter)" ng-focus="pill.activateActions()" ng-blur="pill.deactivateActions()" - aria-label="Remove filter" - tooltip="Remove filter" + aria-label="{{ ::'common.ui.filterBar.filterPill.removeFilterAriaLabel' | i18n: { defaultMessage: 'Remove filter' } }}" + tooltip="{{ ::'common.ui.filterBar.filterPill.removeFilterTooltip' | i18n: { defaultMessage: 'Remove filter' } }}" tooltip-append-to-body="true" > @@ -80,14 +84,14 @@ ng-disabled="pill.isControlledByPanel()" ng-focus="pill.activateActions()" ng-blur="pill.deactivateActions()" - aria-label="Edit filter" - tooltip="Edit filter" + aria-label="{{ ::'common.ui.filterBar.filterPill.editFilterAriaLabel' | i18n: { defaultMessage: 'Edit filter' } }}" + tooltip="{{ ::'common.ui.filterBar.filterPill.editFilterTooltip' | i18n: { defaultMessage: 'Edit filter' } }}" tooltip-append-to-body="true" data-test-subj="editFilter" > diff --git a/src/ui/public/filter_bar/filter_pill/filter_pill.js b/src/ui/public/filter_bar/filter_pill/filter_pill.js index 03b19427af930..cd15d9df7d888 100644 --- a/src/ui/public/filter_bar/filter_pill/filter_pill.js +++ b/src/ui/public/filter_bar/filter_pill/filter_pill.js @@ -23,7 +23,7 @@ import { uiModules } from '../../modules'; const module = uiModules.get('kibana'); -module.directive('filterPill', function () { +module.directive('filterPill', function (i18n) { return { template, restrict: 'E', @@ -51,6 +51,20 @@ module.directive('filterPill', function () { return _.has(this.filter, 'meta.controlledBy'); }; + this.i18n = { + enableFilterAriaLabel: i18n('common.ui.filterBar.filterPill.enableFilterAriaLabel', { defaultMessage: 'Enable filter' }), + enableFilterTooltip: i18n('common.ui.filterBar.filterPill.enableFilterTooltip', { defaultMessage: 'Enable filter' }), + disableFilterAriaLabel: i18n('common.ui.filterBar.filterPill.disableFilterAriaLabel', { defaultMessage: 'Disable filter' }), + disableFilterTooltip: i18n('common.ui.filterBar.filterPill.disableFilterTooltip', { defaultMessage: 'Disable filter' }), + pinFilterAriaLabel: i18n('common.ui.filterBar.filterPill.pinFilterAriaLabel', { defaultMessage: 'Pin filter' }), + pinFilterTooltip: i18n('common.ui.filterBar.filterPill.pinFilterTooltip', { defaultMessage: 'Pin filter' }), + unpinFilterAriaLabel: i18n('common.ui.filterBar.filterPill.unpinFilterAriaLabel', { defaultMessage: 'Unpin filter' }), + unpinFilterTooltip: i18n('common.ui.filterBar.filterPill.unpinFilterTooltip', { defaultMessage: 'Unpin filter' }), + includeMatchesAriaLabel: i18n('common.ui.filterBar.filterPill.includeMatchesAriaLabel', { defaultMessage: 'Include matches' }), + includeMatchesTooltip: i18n('common.ui.filterBar.filterPill.includeMatchesTooltip', { defaultMessage: 'Include matches' }), + excludeMatchesAriaLabel: i18n('common.ui.filterBar.filterPill.excludeMatchesAriaLabel', { defaultMessage: 'Exclude matches' }), + excludeMatchesTooltip: i18n('common.ui.filterBar.filterPill.excludeMatchesTooltip', { defaultMessage: 'Exclude matches' }), + }; } }; }); diff --git a/src/ui/public/filter_editor/filter_editor.html b/src/ui/public/filter_editor/filter_editor.html index 0b69a1d5661d8..7070f6ac8e653 100644 --- a/src/ui/public/filter_editor/filter_editor.html +++ b/src/ui/public/filter_editor/filter_editor.html @@ -1,16 +1,22 @@
- Add - Edit - filter + +
@@ -18,9 +24,11 @@
@@ -87,7 +99,20 @@ >

- Filters are built using the Elasticsearch Query DSL. + + .

@@ -98,14 +123,14 @@ + i18n-id="common.ui.filterEditor.labelLabel" + i18n-default-message="Label" + > - Cancel - + i18n-id="common.ui.filterEditor.cancelButtonLabel" + i18n-default-message="Cancel" + > + i18n-id="common.ui.filterEditor.saveButtonLabel" + i18n-default-message="Save" + > diff --git a/src/ui/public/filter_editor/filter_field_select.html b/src/ui/public/filter_editor/filter_field_select.html index 65c45b1bae7b0..1aef71c2b0bb9 100644 --- a/src/ui/public/filter_editor/filter_field_select.html +++ b/src/ui/public/filter_editor/filter_field_select.html @@ -4,7 +4,7 @@ on-select="onSelect({ field: field })" uis-open-close="resetLimit()" > - + - + {{$select.selected.name}} diff --git a/src/ui/public/filter_editor/lib/filter_operators.js b/src/ui/public/filter_editor/lib/filter_operators.js index 140928eb1912d..a7a81aff3ddcf 100644 --- a/src/ui/public/filter_editor/lib/filter_operators.js +++ b/src/ui/public/filter_editor/lib/filter_operators.js @@ -19,48 +19,66 @@ import _ from 'lodash'; +import { i18n } from '@kbn/i18n'; + export const FILTER_OPERATORS = [ { - name: 'is', + name: i18n.translate('common.ui.filterEditor.operators.isLabel', { + defaultMessage: 'is' + }), type: 'phrase', negate: false, }, { - name: 'is not', + name: i18n.translate('common.ui.filterEditor.operators.isNotLabel', { + defaultMessage: 'is not' + }), type: 'phrase', negate: true, }, { - name: 'is one of', + name: i18n.translate('common.ui.filterEditor.operators.isOneOfLabel', { + defaultMessage: 'is one of' + }), type: 'phrases', negate: false, fieldTypes: ['string', 'number', 'date', 'ip', 'geo_point', 'geo_shape'] }, { - name: 'is not one of', + name: i18n.translate('common.ui.filterEditor.operators.isNotOneOfLabel', { + defaultMessage: 'is not one of' + }), type: 'phrases', negate: true, fieldTypes: ['string', 'number', 'date', 'ip', 'geo_point', 'geo_shape'] }, { - name: 'is between', + name: i18n.translate('common.ui.filterEditor.operators.isBetweenLabel', { + defaultMessage: 'is between' + }), type: 'range', negate: false, fieldTypes: ['number', 'date', 'ip'], }, { - name: 'is not between', + name: i18n.translate('common.ui.filterEditor.operators.isNotBetweenLabel', { + defaultMessage: 'is not between' + }), type: 'range', negate: true, fieldTypes: ['number', 'date', 'ip'], }, { - name: 'exists', + name: i18n.translate('common.ui.filterEditor.operators.existsLabel', { + defaultMessage: 'exists' + }), type: 'exists', negate: false, }, { - name: 'does not exist', + name: i18n.translate('common.ui.filterEditor.operators.doesNotExistLabel', { + defaultMessage: 'does not exist' + }), type: 'exists', negate: true, }, diff --git a/src/ui/public/filter_editor/params_editor/filter_params_phrase_editor.html b/src/ui/public/filter_editor/params_editor/filter_params_phrase_editor.html index 36d509f28dece..67d8acc8ed682 100644 --- a/src/ui/public/filter_editor/params_editor/filter_params_phrase_editor.html +++ b/src/ui/public/filter_editor/params_editor/filter_params_phrase_editor.html @@ -5,7 +5,7 @@ spinner-enabled="true" spinner-class="kuiIcon kuiIcon--basic fa-spinner fa-spin" > - + - Accepted date formats - + i18n-id="common.ui.filterEditor.filterParamsPhraseEditor.acceptedDateFormatsLinkText" + i18n-default-message="Accepted date formats" + > diff --git a/src/ui/public/filter_editor/params_editor/filter_params_phrases_editor.html b/src/ui/public/filter_editor/params_editor/filter_params_phrases_editor.html index 4a2935c4ba0b7..d2caea1066db1 100644 --- a/src/ui/public/filter_editor/params_editor/filter_params_phrases_editor.html +++ b/src/ui/public/filter_editor/params_editor/filter_params_phrases_editor.html @@ -6,7 +6,7 @@ spinner-class="kuiIcon kuiIcon--basic fa-spinner fa-spin" > - Accepted date formats - + i18n-id="common.ui.filterEditor.filterParamsRangeEditor.acceptedDateFormatsLinkText" + i18n-default-message="Accepted date formats" + > diff --git a/src/ui/public/notify/partials/toaster.html b/src/ui/public/notify/partials/toaster.html index 49b229d9c3ab8..5d5b0ab72af83 100644 --- a/src/ui/public/notify/partials/toaster.html +++ b/src/ui/public/notify/partials/toaster.html @@ -85,10 +85,14 @@ ng-if="notif.isTimed()" class="kbnToaster__countdown" ng-click="notif.cancelTimer()" - title="Stop" + title="{{ ::'common.ui.notify.toaster.stopCountdownButtonTooltip' | i18n: { defaultMessage: 'Stop' } }}" > - {{ notif.timeRemaining }}s + diff --git a/src/ui/public/pager_control/components/tool_bar_pager_text/tool_bar_pager_text.html b/src/ui/public/pager_control/components/tool_bar_pager_text/tool_bar_pager_text.html index b7e1014282199..72bbc3833630b 100644 --- a/src/ui/public/pager_control/components/tool_bar_pager_text/tool_bar_pager_text.html +++ b/src/ui/public/pager_control/components/tool_bar_pager_text/tool_bar_pager_text.html @@ -1,3 +1,11 @@
- {{ toolBarPagerText.startItem | number }}–{{ toolBarPagerText.endItem | number }} of {{ toolBarPagerText.totalItems | number }} +
\ No newline at end of file diff --git a/src/ui/public/persisted_log/persisted_log.ts b/src/ui/public/persisted_log/persisted_log.ts index 209607b5a9a09..0824d17757311 100644 --- a/src/ui/public/persisted_log/persisted_log.ts +++ b/src/ui/public/persisted_log/persisted_log.ts @@ -18,6 +18,8 @@ */ import _ from 'lodash'; +import * as Rx from 'rxjs'; +import { map } from 'rxjs/operators'; import { Storage } from 'ui/storage'; const localStorage = new Storage(window.localStorage); @@ -40,6 +42,8 @@ export class PersistedLog { public storage: Storage; public items: T[]; + private update$ = new Rx.BehaviorSubject(undefined); + constructor(name: string, options: PersistedLogOptions = {}, storage = localStorage) { this.name = name; this.maxLength = @@ -76,10 +80,15 @@ export class PersistedLog { // persist the stack this.storage.set(this.name, this.items); + this.update$.next(undefined); return this.items; } public get() { return _.cloneDeep(this.items); } + + public get$() { + return this.update$.pipe(map(() => this.get())); + } } diff --git a/src/ui/public/persisted_log/recently_accessed.ts b/src/ui/public/persisted_log/recently_accessed.ts index 906646c6b49ab..45309db879ed2 100644 --- a/src/ui/public/persisted_log/recently_accessed.ts +++ b/src/ui/public/persisted_log/recently_accessed.ts @@ -51,6 +51,10 @@ class RecentlyAccessed { public get() { return this.history.get(); } + + public get$() { + return this.history.get$(); + } } export const recentlyAccessed = new RecentlyAccessed(); diff --git a/src/ui/public/query_bar/_index.scss b/src/ui/public/query_bar/_index.scss index 81a69fd89db99..b6634debbfa96 100644 --- a/src/ui/public/query_bar/_index.scss +++ b/src/ui/public/query_bar/_index.scss @@ -1,4 +1,4 @@ // SASSTODO: Formalize this color in Kibana's styling constants $typeaheadConjunctionColor: #7800A6; -@import 'components/typeahead/index'; \ No newline at end of file +@import './components/index'; diff --git a/src/ui/public/query_bar/components/__snapshots__/query_bar.test.tsx.snap b/src/ui/public/query_bar/components/__snapshots__/query_bar.test.tsx.snap index fb092e9f684c0..ec7b3c074e89b 100644 --- a/src/ui/public/query_bar/components/__snapshots__/query_bar.test.tsx.snap +++ b/src/ui/public/query_bar/components/__snapshots__/query_bar.test.tsx.snap @@ -3,6 +3,7 @@ exports[`QueryBar Should disable autoFocus on EuiFieldText when disableAutoFocus prop is true 1`] = ` - - - + /> `; @@ -112,6 +105,7 @@ exports[`QueryBar Should disable autoFocus on EuiFieldText when disableAutoFocus exports[`QueryBar Should pass the query language to the language switcher 1`] = ` - - - + /> `; @@ -221,6 +207,7 @@ exports[`QueryBar Should pass the query language to the language switcher 1`] = exports[`QueryBar Should render the given query 1`] = ` - - - + /> `; diff --git a/src/ui/public/query_bar/components/_index.scss b/src/ui/public/query_bar/components/_index.scss new file mode 100644 index 0000000000000..e17c416c13546 --- /dev/null +++ b/src/ui/public/query_bar/components/_index.scss @@ -0,0 +1,2 @@ +@import './query_bar'; +@import './typeahead/index'; diff --git a/src/ui/public/query_bar/components/_query_bar.scss b/src/ui/public/query_bar/components/_query_bar.scss new file mode 100644 index 0000000000000..f104c20daf064 --- /dev/null +++ b/src/ui/public/query_bar/components/_query_bar.scss @@ -0,0 +1,14 @@ +@include euiBreakpoint('xs', 's') { + .kbnQueryBar--withDatePicker { + > :last-child { + // EUI Flexbox adds too much margin between responded items, this just moves the last one up + margin-top: -$euiSize; + } + } +} + +@include euiBreakpoint('m', 'l', 'xl') { + .kbnQueryBar__datePickerWrapper { + max-width: 40vw; + } +} diff --git a/src/ui/public/query_bar/components/query_bar.test.tsx b/src/ui/public/query_bar/components/query_bar.test.tsx index 363464a5ca96e..8df14e4cf3210 100644 --- a/src/ui/public/query_bar/components/query_bar.test.tsx +++ b/src/ui/public/query_bar/components/query_bar.test.tsx @@ -207,8 +207,14 @@ describe('QueryBar', () => { component.find(QueryLanguageSwitcher).simulate('selectLanguage', 'lucene'); expect(mockStorage.set).toHaveBeenCalledWith('kibana.userQueryLanguage', 'lucene'); expect(mockCallback).toHaveBeenCalledWith({ - query: '', - language: 'lucene', + dateRange: { + from: 'now-15m', + to: 'now', + }, + query: { + query: '', + language: 'lucene', + }, }); }); @@ -235,8 +241,14 @@ describe('QueryBar', () => { expect(mockCallback).toHaveBeenCalledTimes(1); expect(mockCallback).toHaveBeenCalledWith({ - query: 'extension:jpg', - language: 'kuery', + dateRange: { + from: 'now-15m', + to: 'now', + }, + query: { + query: 'extension:jpg', + language: 'kuery', + }, }); }); diff --git a/src/ui/public/query_bar/components/query_bar.tsx b/src/ui/public/query_bar/components/query_bar.tsx index af91fea37eb43..5ffae8dc24743 100644 --- a/src/ui/public/query_bar/components/query_bar.tsx +++ b/src/ui/public/query_bar/components/query_bar.tsx @@ -19,12 +19,15 @@ import { IndexPattern } from 'ui/index_patterns'; -import { compact, debounce, isEqual } from 'lodash'; +import classNames from 'classnames'; +import _ from 'lodash'; +import { compact, debounce, get, isEqual } from 'lodash'; import React, { Component } from 'react'; import { getFromLegacyIndexPattern } from 'ui/index_patterns/static_utils'; import { kfetch } from 'ui/kfetch'; import { PersistedLog } from 'ui/persisted_log'; import { Storage } from 'ui/storage'; +import { timeHistory } from 'ui/timefilter/time_history'; import { AutocompleteSuggestion, AutocompleteSuggestionType, @@ -36,15 +39,12 @@ import { matchPairs } from '../lib/match_pairs'; import { QueryLanguageSwitcher } from './language_switcher'; import { SuggestionsComponent } from './typeahead/suggestions_component'; -import { - EuiButton, - EuiFieldText, - EuiFlexGroup, - EuiFlexItem, - EuiOutsideClickDetector, -} from '@elastic/eui'; +import { EuiFieldText, EuiFlexGroup, EuiFlexItem, EuiOutsideClickDetector } from '@elastic/eui'; + +// @ts-ignore +import { EuiSuperDatePicker, EuiSuperUpdateButton } from '@elastic/eui'; -import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react'; +import { InjectedIntl, injectI18n } from '@kbn/i18n/react'; const KEY_CODES = { LEFT: 37, @@ -66,14 +66,25 @@ interface Query { language: string; } +interface DateRange { + from: string; + to: string; +} + interface Props { query: Query; - onSubmit: (query: { query: string | object; language: string }) => void; + onSubmit: (payload: { dateRange: DateRange; query: Query }) => void; disableAutoFocus?: boolean; appName: string; indexPatterns: IndexPattern[]; store: Storage; intl: InjectedIntl; + showDatePicker?: boolean; + dateRangeFrom?: string; + dateRangeTo?: string; + isRefreshPaused?: boolean; + refreshInterval?: number; + onRefreshChange?: (isPaused: boolean, refreshInterval: number) => void; } interface State { @@ -84,6 +95,9 @@ interface State { suggestions: AutocompleteSuggestion[]; suggestionLimit: number; currentProps?: Props; + dateRangeFrom: string; + dateRangeTo: string; + isDateRangeInvalid: boolean; } export class QueryBarUI extends Component { @@ -92,25 +106,41 @@ export class QueryBarUI extends Component { return null; } + let nextQuery = null; if (nextProps.query.query !== prevState.query.query) { - return { - query: { - query: toUser(nextProps.query.query), - language: nextProps.query.language, - }, - currentProps: nextProps, + nextQuery = { + query: toUser(nextProps.query.query), + language: nextProps.query.language, }; } else if (nextProps.query.language !== prevState.query.language) { - return { - query: { - query: '', - language: nextProps.query.language, - }, - currentProps: nextProps, + nextQuery = { + query: '', + language: nextProps.query.language, }; } - return { currentProps: nextProps }; + let nextDateRange = null; + if ( + nextProps.dateRangeFrom !== get(prevState, 'currentProps.dateRangeFrom') || + nextProps.dateRangeTo !== get(prevState, 'currentProps.dateRangeTo') + ) { + nextDateRange = { + dateRangeFrom: nextProps.dateRangeFrom, + dateRangeTo: nextProps.dateRangeTo, + }; + } + + const nextState: any = { + currentProps: nextProps, + }; + if (nextQuery) { + nextState.query = nextQuery; + } + if (nextDateRange) { + nextState.dateRangeFrom = nextDateRange.dateRangeFrom; + nextState.dateRangeTo = nextDateRange.dateRangeTo; + } + return nextState; } /* @@ -136,6 +166,9 @@ export class QueryBarUI extends Component { index: null, suggestions: [], suggestionLimit: 50, + dateRangeFrom: _.get(this.props, 'dateRangeFrom', 'now-15m'), + dateRangeTo: _.get(this.props, 'dateRangeTo', 'now'), + isDateRangeInvalid: false, }; public updateSuggestions = debounce(async () => { @@ -151,7 +184,15 @@ export class QueryBarUI extends Component { private persistedLog: PersistedLog | null = null; public isDirty = () => { - return this.state.query.query !== this.props.query.query; + if (!this.props.showDatePicker) { + return this.state.query.query !== this.props.query.query; + } + + return ( + this.state.query.query !== this.props.query.query || + this.state.dateRangeFrom !== this.props.dateRangeFrom || + this.state.dateRangeTo !== this.props.dateRangeTo + ); }; public increaseLimit = () => { @@ -322,6 +363,22 @@ export class QueryBarUI extends Component { this.onInputChange(event.target.value); }; + public onTimeChange = ({ + start, + end, + isInvalid, + }: { + start: string; + end: string; + isInvalid: boolean; + }) => { + this.setState({ + dateRangeFrom: start, + dateRangeTo: end, + isDateRangeInvalid: isInvalid, + }); + }; + public onKeyUp = (event: React.KeyboardEvent) => { if ([KEY_CODES.LEFT, KEY_CODES.RIGHT, KEY_CODES.HOME, KEY_CODES.END].includes(event.keyCode)) { this.setState({ isSuggestionsVisible: true }); @@ -408,9 +465,20 @@ export class QueryBarUI extends Component { this.persistedLog.add(this.state.query.query); } + timeHistory.add({ + from: this.state.dateRangeFrom, + to: this.state.dateRangeTo, + }); + this.props.onSubmit({ - query: fromUser(this.state.query.query), - language: this.state.query.language, + query: { + query: fromUser(this.state.query.query), + language: this.state.query.language, + }, + dateRange: { + from: this.state.dateRangeFrom, + to: this.state.dateRangeTo, + }, }); this.setState({ isSuggestionsVisible: false }); }; @@ -427,8 +495,14 @@ export class QueryBarUI extends Component { this.props.store.set('kibana.userQueryLanguage', language); this.props.onSubmit({ - query: '', - language, + query: { + query: '', + language, + }, + dateRange: { + from: this.state.dateRangeFrom, + to: this.state.dateRangeTo, + }, }); }; @@ -462,8 +536,16 @@ export class QueryBarUI extends Component { } public render() { + const classes = classNames('kbnQueryBar', { + 'kbnQueryBar--withDatePicker': this.props.showDatePicker, + }); + return ( - + {/* position:relative required on container so the suggestions appear under the query bar*/} @@ -533,30 +615,74 @@ export class QueryBarUI extends Component { - - - {this.isDirty() ? ( - - ) : ( - - )} - - + {this.renderUpdateButton()} ); } + + private renderUpdateButton() { + const button = ( + + ); + if (this.props.showDatePicker) { + return ( + + {this.renderDatePicker()} + {button} + + ); + } else { + return button; + } + } + + private renderDatePicker() { + if (!this.props.showDatePicker) { + return null; + } + + const recentlyUsedRanges = timeHistory + .get() + .map(({ from, to }: { from: string; to: string }) => { + return { + start: from, + end: to, + }; + }); + + const commonlyUsedRanges = config + .get('timepicker:quickRanges') + .map(({ from, to, display }: { from: string; to: string; display: string }) => { + return { + start: from, + end: to, + label: display, + }; + }); + + return ( + + + + ); + } } +// @ts-ignore export const QueryBar = injectI18n(QueryBarUI); diff --git a/src/ui/public/saved_objects/saved_object.js b/src/ui/public/saved_objects/saved_object.js index 9287b9e313d8f..ed3bbfe9b0fb1 100644 --- a/src/ui/public/saved_objects/saved_object.js +++ b/src/ui/public/saved_objects/saved_object.js @@ -20,11 +20,12 @@ import _ from 'lodash'; export class SavedObject { - constructor(client, { id, type, version, attributes, error, migrationVersion } = {}) { + constructor(client, { id, type, version, attributes, error, migrationVersion, references } = {}) { this._client = client; this.id = id; this.type = type; this.attributes = attributes || {}; + this.references = references || []; this._version = version; this.migrationVersion = migrationVersion; if (error) { @@ -46,9 +47,17 @@ export class SavedObject { save() { if (this.id) { - return this._client.update(this.type, this.id, this.attributes, { migrationVersion: this.migrationVersion }); + return this._client.update( + this.type, + this.id, + this.attributes, + { + migrationVersion: this.migrationVersion, + references: this.references, + }, + ); } else { - return this._client.create(this.type, this.attributes, { migrationVersion: this.migrationVersion }); + return this._client.create(this.type, this.attributes, { migrationVersion: this.migrationVersion, references: this.references }); } } diff --git a/src/ui/public/saved_objects/saved_objects_client.js b/src/ui/public/saved_objects/saved_objects_client.js index eba1acebabe4b..0ee4c2ff47ad3 100644 --- a/src/ui/public/saved_objects/saved_objects_client.js +++ b/src/ui/public/saved_objects/saved_objects_client.js @@ -51,6 +51,7 @@ export class SavedObjectsClient { * @property {string} [options.id] - force id on creation, not recommended * @property {boolean} [options.overwrite=false] * @property {object} [options.migrationVersion] + * @property {array} [options.references] [{ name, type, id }] * @returns {promise} - SavedObject({ id, type, version, attributes }) */ create = (type, attributes = {}, options = {}) => { @@ -61,7 +62,17 @@ export class SavedObjectsClient { const path = this._getPath([type, options.id]); const query = _.pick(options, ['overwrite']); - return this._request({ method: 'POST', path, query, body: { attributes, migrationVersion: options.migrationVersion } }) + return this + ._request({ + method: 'POST', + path, + query, + body: { + attributes, + migrationVersion: options.migrationVersion, + references: options.references, + }, + }) .catch(error => { if (isAutoCreateIndexError(error)) { return showAutoCreateIndexErrorPage(); @@ -75,7 +86,7 @@ export class SavedObjectsClient { /** * Creates multiple documents at once * - * @param {array} objects - [{ type, id, attributes, migrationVersion }] + * @param {array} objects - [{ type, id, attributes, references, migrationVersion }] * @param {object} [options={}] * @property {boolean} [options.overwrite=false] * @returns {promise} - { savedObjects: [{ id, type, version, attributes, error: { message } }]} @@ -117,6 +128,7 @@ export class SavedObjectsClient { * @property {integer} [options.page=1] * @property {integer} [options.perPage=20] * @property {array} options.fields + * @property {object} [options.hasReference] - { type, id } * @returns {promise} - { savedObjects: [ SavedObject({ id, type, version, attributes }) ]} */ find = (options = {}) => { @@ -178,9 +190,10 @@ export class SavedObjectsClient { * @param {object} options * @prop {integer} options.version - ensures version matches that of persisted object * @prop {object} options.migrationVersion - The optional migrationVersion of this document + * @prop {array} option.references - the references of the saved object * @returns {promise} */ - update(type, id, attributes, { version, migrationVersion } = {}) { + update(type, id, attributes, { version, migrationVersion, references } = {}) { if (!type || !id || !attributes) { return Promise.reject(new Error('requires type, id and attributes')); } @@ -189,6 +202,7 @@ export class SavedObjectsClient { const body = { attributes, migrationVersion, + references, version }; diff --git a/src/ui/public/styles/disable_animations/disable_animations.css b/src/ui/public/styles/disable_animations/disable_animations.css index 9cf9d9eb4e5f2..a5ca0f417e669 100644 --- a/src/ui/public/styles/disable_animations/disable_animations.css +++ b/src/ui/public/styles/disable_animations/disable_animations.css @@ -11,4 +11,7 @@ -webkit-transition-duration: 0s !important; transition-duration: 0s !important; + + -webkit-transition-delay: 0s !important; + transition-delay: 0s !important; } diff --git a/src/legacy/core_plugins/kibana/server/lib/export/collect_dashboards.js b/src/ui/public/timefilter/time_history.d.ts similarity index 56% rename from src/legacy/core_plugins/kibana/server/lib/export/collect_dashboards.js rename to src/ui/public/timefilter/time_history.d.ts index 969c2add1364d..4a7331dbb8c58 100644 --- a/src/legacy/core_plugins/kibana/server/lib/export/collect_dashboards.js +++ b/src/ui/public/timefilter/time_history.d.ts @@ -17,27 +17,15 @@ * under the License. */ -import { collectPanels } from './collect_panels'; - -export async function collectDashboards(savedObjectsClient, ids) { - - if (ids.length === 0) return []; - - const objects = ids.map(id => { - return { - type: 'dashboard', - id: id - }; - }); - - const { saved_objects: savedObjects } = await savedObjectsClient.bulkGet(objects); - const results = await Promise.all(savedObjects.map(d => collectPanels(savedObjectsClient, d))); - - return results - .reduce((acc, result) => acc.concat(result), []) - .reduce((acc, obj) => { - if (!acc.find(o => o.id === obj.id)) acc.push(obj); - return acc; - }, []); +interface TimeRange { + from: string; + to: string; + mode?: string; +} +export interface TimeHistory { + add: (options: TimeRange) => void; + get: () => TimeRange[]; } + +export const timeHistory: TimeHistory; diff --git a/test/api_integration/apis/management/saved_objects/relationships.js b/test/api_integration/apis/management/saved_objects/relationships.js index 45b0a8b82e4a2..e31a5fe3aaa7e 100644 --- a/test/api_integration/apis/management/saved_objects/relationships.js +++ b/test/api_integration/apis/management/saved_objects/relationships.js @@ -40,8 +40,8 @@ export default function ({ getService }) { after(() => esArchiver.unload('management/saved_objects')); const SEARCH_RESPONSE_SCHEMA = Joi.object().keys({ - visualizations: GENERIC_RESPONSE_SCHEMA, - indexPatterns: GENERIC_RESPONSE_SCHEMA, + visualization: GENERIC_RESPONSE_SCHEMA, + 'index-pattern': GENERIC_RESPONSE_SCHEMA, }); describe('searches', async () => { @@ -61,13 +61,13 @@ export default function ({ getService }) { .expect(200) .then(resp => { expect(resp.body).to.eql({ - visualizations: [ + visualization: [ { id: 'a42c0580-3224-11e8-a572-ffca06da1357', title: 'VisualizationFromSavedSearch', }, ], - indexPatterns: [ + 'index-pattern': [ { id: '8963ca30-3224-11e8-a572-ffca06da1357', title: 'saved_objects*', @@ -85,7 +85,7 @@ export default function ({ getService }) { describe('dashboards', async () => { const DASHBOARD_RESPONSE_SCHEMA = Joi.object().keys({ - visualizations: GENERIC_RESPONSE_SCHEMA, + visualization: GENERIC_RESPONSE_SCHEMA, }); it('should validate dashboard response schema', async () => { @@ -104,7 +104,7 @@ export default function ({ getService }) { .expect(200) .then(resp => { expect(resp.body).to.eql({ - visualizations: [ + visualization: [ { id: 'add810b0-3224-11e8-a572-ffca06da1357', title: 'Visualization', @@ -128,7 +128,8 @@ export default function ({ getService }) { describe('visualizations', async () => { const VISUALIZATIONS_RESPONSE_SCHEMA = Joi.object().keys({ - dashboards: GENERIC_RESPONSE_SCHEMA, + dashboard: GENERIC_RESPONSE_SCHEMA, + search: GENERIC_RESPONSE_SCHEMA, }); it('should validate visualization response schema', async () => { @@ -147,7 +148,13 @@ export default function ({ getService }) { .expect(200) .then(resp => { expect(resp.body).to.eql({ - dashboards: [ + search: [ + { + id: '960372e0-3224-11e8-a572-ffca06da1357', + title: 'OneRecord' + }, + ], + dashboard: [ { id: 'b70c7ae0-3224-11e8-a572-ffca06da1357', title: 'Dashboard', @@ -166,8 +173,8 @@ export default function ({ getService }) { describe('index patterns', async () => { const INDEX_PATTERN_RESPONSE_SCHEMA = Joi.object().keys({ - searches: GENERIC_RESPONSE_SCHEMA, - visualizations: GENERIC_RESPONSE_SCHEMA, + search: GENERIC_RESPONSE_SCHEMA, + visualization: GENERIC_RESPONSE_SCHEMA, }); it('should validate visualization response schema', async () => { @@ -186,13 +193,13 @@ export default function ({ getService }) { .expect(200) .then(resp => { expect(resp.body).to.eql({ - searches: [ + search: [ { id: '960372e0-3224-11e8-a572-ffca06da1357', title: 'OneRecord', }, ], - visualizations: [ + visualization: [ { id: 'add810b0-3224-11e8-a572-ffca06da1357', title: 'Visualization', diff --git a/test/api_integration/apis/saved_objects/bulk_create.js b/test/api_integration/apis/saved_objects/bulk_create.js index 153dda4691fa6..074af9f775dde 100644 --- a/test/api_integration/apis/saved_objects/bulk_create.js +++ b/test/api_integration/apis/saved_objects/bulk_create.js @@ -69,7 +69,8 @@ export default function ({ getService }) { version: 1, attributes: { title: 'A great new dashboard' - } + }, + references: [], }, ] }); @@ -101,7 +102,8 @@ export default function ({ getService }) { version: 1, attributes: { title: 'An existing visualization' - } + }, + references: [], }, { type: 'dashboard', @@ -110,7 +112,8 @@ export default function ({ getService }) { version: 1, attributes: { title: 'A great new dashboard' - } + }, + references: [], }, ] }); diff --git a/test/api_integration/apis/saved_objects/bulk_get.js b/test/api_integration/apis/saved_objects/bulk_get.js index 68773e5124039..da208a5cbe70a 100644 --- a/test/api_integration/apis/saved_objects/bulk_get.js +++ b/test/api_integration/apis/saved_objects/bulk_get.js @@ -68,7 +68,15 @@ export default function ({ getService }) { visState: resp.body.saved_objects[0].attributes.visState, uiStateJSON: resp.body.saved_objects[0].attributes.uiStateJSON, kibanaSavedObjectMeta: resp.body.saved_objects[0].attributes.kibanaSavedObjectMeta - } + }, + migrationVersion: { + visualization: '7.0.0', + }, + references: [{ + name: 'kibanaSavedObjectMeta.searchSourceJSON.index', + type: 'index-pattern', + id: '91200a00-9efd-11e7-acb3-3dab96693fab', + }], }, { id: 'does not exist', @@ -86,7 +94,8 @@ export default function ({ getService }) { attributes: { buildNum: 8467, defaultIndex: '91200a00-9efd-11e7-acb3-3dab96693fab' - } + }, + references: [], } ] }); diff --git a/test/api_integration/apis/saved_objects/create.js b/test/api_integration/apis/saved_objects/create.js index 379539928ebaf..a4e66318fb01e 100644 --- a/test/api_integration/apis/saved_objects/create.js +++ b/test/api_integration/apis/saved_objects/create.js @@ -54,7 +54,11 @@ export default function ({ getService }) { version: 1, attributes: { title: 'My favorite vis' - } + }, + migrationVersion: { + visualization: '7.0.0', + }, + references: [], }); }); }); @@ -95,7 +99,11 @@ export default function ({ getService }) { version: 1, attributes: { title: 'My favorite vis' - } + }, + migrationVersion: { + visualization: '7.0.0', + }, + references: [], }); }); diff --git a/test/api_integration/apis/saved_objects/find.js b/test/api_integration/apis/saved_objects/find.js index c9b1e9fc73f4a..515f4501517bc 100644 --- a/test/api_integration/apis/saved_objects/find.js +++ b/test/api_integration/apis/saved_objects/find.js @@ -45,7 +45,8 @@ export default function ({ getService }) { version: 1, attributes: { 'title': 'Count of requests' - } + }, + references: [], } ] }); diff --git a/test/api_integration/apis/saved_objects/get.js b/test/api_integration/apis/saved_objects/get.js index 22ea4798f4324..0734918f5f3e4 100644 --- a/test/api_integration/apis/saved_objects/get.js +++ b/test/api_integration/apis/saved_objects/get.js @@ -42,6 +42,9 @@ export default function ({ getService }) { type: 'visualization', updated_at: '2017-09-21T18:51:23.794Z', version: resp.body.version, + migrationVersion: { + visualization: '7.0.0', + }, attributes: { title: 'Count of requests', description: '', @@ -50,7 +53,12 @@ export default function ({ getService }) { visState: resp.body.attributes.visState, uiStateJSON: resp.body.attributes.uiStateJSON, kibanaSavedObjectMeta: resp.body.attributes.kibanaSavedObjectMeta - } + }, + references: [{ + type: 'index-pattern', + name: 'kibanaSavedObjectMeta.searchSourceJSON.index', + id: '91200a00-9efd-11e7-acb3-3dab96693fab', + }], }); }) )); diff --git a/test/api_integration/apis/saved_objects/migrations.js b/test/api_integration/apis/saved_objects/migrations.js index 260609d3b881e..a4993658837f2 100644 --- a/test/api_integration/apis/saved_objects/migrations.js +++ b/test/api_integration/apis/saved_objects/migrations.js @@ -85,11 +85,11 @@ export default ({ getService }) => { // The docs in the alias have been migrated assert.deepEqual(await fetchDocs({ callCluster, index }), [ - { id: 'bar:i', type: 'bar', migrationVersion: { bar: '1.9.0' }, bar: { mynum: 68 } }, - { id: 'bar:o', type: 'bar', migrationVersion: { bar: '1.9.0' }, bar: { mynum: 6 } }, - { id: 'baz:u', type: 'baz', baz: { title: 'Terrific!' } }, - { id: 'foo:a', type: 'foo', migrationVersion: { foo: '1.0.0' }, foo: { name: 'FOO A' } }, - { id: 'foo:e', type: 'foo', migrationVersion: { foo: '1.0.0' }, foo: { name: 'FOOEY' } }, + { id: 'bar:i', type: 'bar', migrationVersion: { bar: '1.9.0' }, bar: { mynum: 68 }, references: [] }, + { id: 'bar:o', type: 'bar', migrationVersion: { bar: '1.9.0' }, bar: { mynum: 6 }, references: [] }, + { id: 'baz:u', type: 'baz', baz: { title: 'Terrific!' }, references: [] }, + { id: 'foo:a', type: 'foo', migrationVersion: { foo: '1.0.0' }, foo: { name: 'FOO A' }, references: [] }, + { id: 'foo:e', type: 'foo', migrationVersion: { foo: '1.0.0' }, foo: { name: 'FOOEY' }, references: [] }, ]); }); @@ -131,10 +131,10 @@ export default ({ getService }) => { // The index for the initial migration has not been destroyed... assert.deepEqual(await fetchDocs({ callCluster, index: `${index}_2` }), [ - { id: 'bar:i', type: 'bar', migrationVersion: { bar: '1.9.0' }, bar: { mynum: 68 } }, - { id: 'bar:o', type: 'bar', migrationVersion: { bar: '1.9.0' }, bar: { mynum: 6 } }, - { id: 'foo:a', type: 'foo', migrationVersion: { foo: '1.0.0' }, foo: { name: 'FOO A' } }, - { id: 'foo:e', type: 'foo', migrationVersion: { foo: '1.0.0' }, foo: { name: 'FOOEY' } }, + { id: 'bar:i', type: 'bar', migrationVersion: { bar: '1.9.0' }, bar: { mynum: 68 }, references: [] }, + { id: 'bar:o', type: 'bar', migrationVersion: { bar: '1.9.0' }, bar: { mynum: 6 }, references: [] }, + { id: 'foo:a', type: 'foo', migrationVersion: { foo: '1.0.0' }, foo: { name: 'FOO A' }, references: [] }, + { id: 'foo:e', type: 'foo', migrationVersion: { foo: '1.0.0' }, foo: { name: 'FOOEY' }, references: [] }, ]); // The docs were migrated again... @@ -144,15 +144,17 @@ export default ({ getService }) => { type: 'bar', migrationVersion: { bar: '2.3.4' }, bar: { mynum: 68, name: 'NAME i' }, + references: [], }, { id: 'bar:o', type: 'bar', migrationVersion: { bar: '2.3.4' }, bar: { mynum: 6, name: 'NAME o' }, + references: [], }, - { id: 'foo:a', type: 'foo', migrationVersion: { foo: '2.0.1' }, foo: { name: 'FOO Av2' } }, - { id: 'foo:e', type: 'foo', migrationVersion: { foo: '2.0.1' }, foo: { name: 'FOOEYv2' } }, + { id: 'foo:a', type: 'foo', migrationVersion: { foo: '2.0.1' }, foo: { name: 'FOO Av2' }, references: [] }, + { id: 'foo:e', type: 'foo', migrationVersion: { foo: '2.0.1' }, foo: { name: 'FOOEYv2' }, references: [] }, ]); }); @@ -203,7 +205,7 @@ export default ({ getService }) => { // The docs in the alias have been migrated assert.deepEqual(await fetchDocs({ callCluster, index }), [ - { id: 'foo:lotr', type: 'foo', migrationVersion: { foo: '1.0.0' }, foo: { name: 'LOTR' } }, + { id: 'foo:lotr', type: 'foo', migrationVersion: { foo: '1.0.0' }, foo: { name: 'LOTR' }, references: [] }, ]); }); }); diff --git a/test/api_integration/apis/saved_objects/update.js b/test/api_integration/apis/saved_objects/update.js index e6ca3d0317bf3..a00ab69783cb0 100644 --- a/test/api_integration/apis/saved_objects/update.js +++ b/test/api_integration/apis/saved_objects/update.js @@ -51,7 +51,8 @@ export default function ({ getService }) { version: 2, attributes: { title: 'My second favorite vis' - } + }, + references: [], }); }); }); diff --git a/test/functional/apps/discover/_discover.js b/test/functional/apps/discover/_discover.js index 64e2a2fdc952e..dad40daf0d6a7 100644 --- a/test/functional/apps/discover/_discover.js +++ b/test/functional/apps/discover/_discover.js @@ -18,6 +18,7 @@ */ import expect from 'expect.js'; +import moment from 'moment'; export default function ({ getService, getPageObjects }) { const log = getService('log'); @@ -153,8 +154,13 @@ export default function ({ getService, getPageObjects }) { await PageObjects.visualize.waitForVisualization(); await PageObjects.discover.brushHistogram(0, 1); await PageObjects.visualize.waitForVisualization(); - const actualTimeString = await PageObjects.header.getPrettyDuration(); - expect(actualTimeString).to.be('September 19th 2015, 23:59:02.606 to September 20th 2015, 02:56:40.744'); + const newFromTime = await PageObjects.header.getFromTime(); + const newToTime = await PageObjects.header.getToTime(); + + const newDurationHours = moment.duration(moment(newToTime) - moment(newFromTime)).asHours(); + if (newDurationHours < 1 || newDurationHours >= 5) { + throw new Error(`expected new duration of ${newDurationHours} hours to be between 1 and 5 hours`); + } }); it('should show correct initial chart interval of Auto', async function () { diff --git a/test/functional/apps/discover/index.js b/test/functional/apps/discover/index.js index 420c0de35115c..6e7aedc250351 100644 --- a/test/functional/apps/discover/index.js +++ b/test/functional/apps/discover/index.js @@ -25,7 +25,7 @@ export default function ({ getService, loadTestFile }) { this.tags('ciGroup6'); before(function () { - return browser.setWindowSize(1200, 800); + return browser.setWindowSize(1250, 800); }); after(function unloadMakelogs() { diff --git a/test/functional/apps/visualize/_pie_chart.js b/test/functional/apps/visualize/_pie_chart.js index dcab846c277a1..3b9442e1ffad0 100644 --- a/test/functional/apps/visualize/_pie_chart.js +++ b/test/functional/apps/visualize/_pie_chart.js @@ -213,6 +213,69 @@ export default function ({ getService, getPageObjects }) { }); }); describe('multi series slice', () => { + before(async () => { + log.debug('navigateToApp visualize'); + await PageObjects.visualize.navigateToNewVisualization(); + log.debug('clickPieChart'); + await PageObjects.visualize.clickPieChart(); + await PageObjects.visualize.clickNewSearch(); + log.debug('Set absolute time range from \"' + fromTime + '\" to \"' + toTime + '\"'); + await PageObjects.header.setAbsoluteRange(fromTime, toTime); + log.debug('select bucket Split Slices'); + await PageObjects.visualize.clickBucket('Split Slices'); + log.debug('Click aggregation Histogram'); + await PageObjects.visualize.selectAggregation('Histogram'); + log.debug('Click field memory'); + await PageObjects.visualize.selectField('memory'); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.common.sleep(1003); + log.debug('setNumericInterval 4000'); + await PageObjects.visualize.setNumericInterval('40000'); + log.debug('Toggle previous editor'); + await PageObjects.visualize.toggleAggregationEditor(2); + await PageObjects.visualize.clickAddBucket(); + log.debug('select bucket Split Slices'); + await PageObjects.visualize.clickBucket('Split Slices'); + await PageObjects.visualize.selectAggregation('Terms'); + await PageObjects.visualize.selectField('geo.dest'); + await PageObjects.visualize.clickGo(); + }); + + it ('should show correct chart', async () => { + const expectedTableData = [ [ '0', '55', 'CN', '14' ], [ '0', '55', 'IN', '9' ], [ '0', '55', 'MX', '3' ], + [ '0', '55', 'US', '3' ], [ '0', '55', 'BR', '2' ], [ '40,000', '50', 'CN', '7' ], + [ '40,000', '50', 'IN', '7' ], [ '40,000', '50', 'US', '5' ], [ '40,000', '50', 'MY', '3' ], + [ '40,000', '50', 'ET', '2' ], [ '80,000', '41', 'CN', '9' ], [ '80,000', '41', 'IN', '4' ], + [ '80,000', '41', 'US', '4' ], [ '80,000', '41', 'BR', '3' ], [ '80,000', '41', 'IT', '2' ], + [ '120,000', '43', 'CN', '8' ], [ '120,000', '43', 'IN', '5' ], [ '120,000', '43', 'US', '4' ], + [ '120,000', '43', 'JP', '3' ], [ '120,000', '43', 'RU', '3' ], [ '160,000', '44', 'CN', '15' ], + [ '160,000', '44', 'IN', '5' ], [ '160,000', '44', 'IQ', '2' ], [ '160,000', '44', 'JP', '2' ], + [ '160,000', '44', 'NG', '2' ], [ '200,000', '40', 'IN', '7' ], [ '200,000', '40', 'CN', '6' ], + [ '200,000', '40', 'MX', '3' ], [ '200,000', '40', 'BR', '2' ], [ '200,000', '40', 'ID', '2' ], + [ '240,000', '46', 'CN', '6' ], [ '240,000', '46', 'IN', '6' ], [ '240,000', '46', 'US', '6' ], + [ '240,000', '46', 'NG', '3' ], [ '240,000', '46', 'CH', '2' ], [ '280,000', '39', 'CN', '11' ], + [ '280,000', '39', 'IN', '5' ], [ '280,000', '39', 'BR', '2' ], [ '280,000', '39', 'IT', '2' ], + [ '280,000', '39', 'NG', '2' ], [ '320,000', '40', 'CN', '7' ], [ '320,000', '40', 'US', '6' ], + [ '320,000', '40', 'MX', '4' ], [ '320,000', '40', 'BD', '2' ], [ '320,000', '40', 'ID', '2' ], + [ '360,000', '47', 'IN', '8' ], [ '360,000', '47', 'CN', '6' ], [ '360,000', '47', 'US', '4' ], + [ '360,000', '47', 'BD', '3' ], [ '360,000', '47', 'BR', '2' ] ]; + + await inspector.open(); + await inspector.setTablePageSize(50); + await inspector.expectTableData(expectedTableData); + await inspector.close(); + }); + + it('should correctly filter on legend', async () => { + const expectedTableData = [ '0', 'CN', '40,000', 'CN', '80,000', 'CN', '120,000', 'CN', '160,000', 'CN', + '200,000', 'CN', '240,000', 'CN', '280,000', 'CN', '320,000', 'CN', '360,000', 'CN' ]; + await PageObjects.visualize.filterLegend('CN'); + await PageObjects.visualize.waitForVisualization(); + await pieChart.expectPieChartLabels(expectedTableData); + await filterBar.removeFilter('geo.dest'); + await PageObjects.visualize.waitForVisualization(); + }); + it('should still showing pie chart when a subseries have zero data', async function () { await PageObjects.visualize.navigateToNewVisualization(); log.debug('clickPieChart'); diff --git a/test/functional/apps/visualize/_vertical_bar_chart.js b/test/functional/apps/visualize/_vertical_bar_chart.js index 330f99d53340d..0954f4e5e7f88 100644 --- a/test/functional/apps/visualize/_vertical_bar_chart.js +++ b/test/functional/apps/visualize/_vertical_bar_chart.js @@ -23,6 +23,7 @@ export default function ({ getService, getPageObjects }) { const log = getService('log'); const retry = getService('retry'); const inspector = getService('inspector'); + const filterBar = getService('filterBar'); const PageObjects = getPageObjects(['common', 'visualize', 'header']); describe('vertical bar chart', function () { @@ -257,6 +258,16 @@ export default function ({ getService, getPageObjects }) { const legendEntries = await PageObjects.visualize.getLegendEntries(); expect(legendEntries).to.eql(expectedEntries); }); + + it ('should correctly filter by legend', async () => { + await PageObjects.visualize.filterLegend('200'); + await PageObjects.visualize.waitForVisualization(); + const legendEntries = await PageObjects.visualize.getLegendEntries(); + const expectedEntries = ['200']; + expect(legendEntries).to.eql(expectedEntries); + await filterBar.removeFilter('response.raw'); + await PageObjects.visualize.waitForVisualization(); + }); }); describe('vertical bar with multiple splits', function () { diff --git a/test/functional/config.js b/test/functional/config.js index aabc2c5eaea32..86d38e1782d0a 100644 --- a/test/functional/config.js +++ b/test/functional/config.js @@ -33,6 +33,7 @@ import { VisualBuilderPageProvider, TimelionPageProvider, SharePageProvider, + TimePickerPageProvider, } from './page_objects'; import { @@ -94,6 +95,7 @@ export default async function ({ readConfigFile }) { visualBuilder: VisualBuilderPageProvider, timelion: TimelionPageProvider, share: SharePageProvider, + timePicker: TimePickerPageProvider, }, services: { es: commonConfig.get('services.es'), diff --git a/test/functional/page_objects/common_page.js b/test/functional/page_objects/common_page.js index bb66169815b50..1a7d671055d24 100644 --- a/test/functional/page_objects/common_page.js +++ b/test/functional/page_objects/common_page.js @@ -90,9 +90,17 @@ export function CommonPageProvider({ getService, getPageObjects }) { } - navigateToApp(appName) { + /** + * @param {string} appName - name of the app + * @param {object} [opts] - optional options object + * @param {object} [opts.appConfig] - overrides for appConfig, e.g. { pathname, hash } + */ + navigateToApp(appName, opts = { appConfig: {} }) { const self = this; - const appUrl = getUrl.noAuth(config.get('servers.kibana'), config.get(['apps', appName])); + const appUrl = getUrl.noAuth(config.get('servers.kibana'), { + ...config.get(['apps', appName]), + ...opts.appConfig, + }); log.debug('navigating to ' + appName + ' url: ' + appUrl); function navigateTo(url) { diff --git a/test/functional/page_objects/home_page.js b/test/functional/page_objects/home_page.js index 54c64e4bf0ed5..7c0d2f7faba63 100644 --- a/test/functional/page_objects/home_page.js +++ b/test/functional/page_objects/home_page.js @@ -22,11 +22,6 @@ export function HomePageProvider({ getService }) { const retry = getService('retry'); class HomePage { - - async clickKibanaIcon() { - await testSubjects.click('kibanaLogo'); - } - async clickSynopsis(title) { await testSubjects.click(`homeSynopsisLink${title}`); } diff --git a/test/functional/page_objects/index.js b/test/functional/page_objects/index.js index 04fc7240480ff..5d561a42e2fa2 100644 --- a/test/functional/page_objects/index.js +++ b/test/functional/page_objects/index.js @@ -32,3 +32,4 @@ export { PointSeriesPageProvider } from './point_series_page'; export { VisualBuilderPageProvider } from './visual_builder_page'; export { TimelionPageProvider } from './timelion_page'; export { SharePageProvider } from './share_page'; +export { TimePickerPageProvider } from './time_picker'; diff --git a/test/functional/page_objects/time_picker.js b/test/functional/page_objects/time_picker.js new file mode 100644 index 0000000000000..77ba480c2907f --- /dev/null +++ b/test/functional/page_objects/time_picker.js @@ -0,0 +1,122 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export function TimePickerPageProvider({ getService }) { + const log = getService('log'); + const retry = getService('retry'); + const find = getService('find'); + const testSubjects = getService('testSubjects'); + + class TimePickerPage { + + async isQuickSelectMenuOpen() { + return await testSubjects.exists('superDatePickerQuickMenu'); + } + + async openQuickSelectTimeMenu() { + log.debug('openQuickSelectTimeMenu'); + const isMenuOpen = await this.isQuickSelectMenuOpen(); + if (!isMenuOpen) { + log.debug('opening quick select menu'); + await retry.try(async () => { + await testSubjects.click('superDatePickerToggleQuickMenuButton'); + }); + } + } + + async closeQuickSelectTimeMenu() { + log.debug('closeQuickSelectTimeMenu'); + const isMenuOpen = await this.isQuickSelectMenuOpen(); + if (isMenuOpen) { + log.debug('closing quick select menu'); + await retry.try(async () => { + await testSubjects.click('superDatePickerToggleQuickMenuButton'); + }); + } + } + + async showStartEndTimes() { + const isShowDatesButton = await testSubjects.exists('superDatePickerShowDatesButton'); + if (isShowDatesButton) { + await testSubjects.click('superDatePickerShowDatesButton'); + } + } + + async getRefreshConfig(keepQuickSelectOpen = false) { + await this.openQuickSelectTimeMenu(); + const interval = await testSubjects.getAttribute('superDatePickerRefreshIntervalInput', 'value'); + + let selectedUnit; + const select = await testSubjects.find('superDatePickerRefreshIntervalUnitsSelect'); + const options = await find.allDescendantDisplayedByCssSelector('option', select); + await Promise.all(options.map(async (optionElement) => { + const isSelected = await optionElement.isSelected(); + if (isSelected) { + selectedUnit = await optionElement.getVisibleText(); + } + })); + + const toggleButtonText = await testSubjects.getVisibleText('superDatePickerToggleRefreshButton'); + if (!keepQuickSelectOpen) { + await this.closeQuickSelectTimeMenu(); + } + + return { + interval, + units: selectedUnit, + isPaused: toggleButtonText === 'Start' ? true : false + }; + } + + async getTimeConfig() { + await this.showStartEndTimes(); + const start = await testSubjects.getVisibleText('superDatePickerstartDatePopoverButton'); + const end = await testSubjects.getVisibleText('superDatePickerendDatePopoverButton'); + return { + start, + end + }; + } + + async pauseAutoRefresh() { + log.debug('pauseAutoRefresh'); + const refreshConfig = await this.getRefreshConfig(true); + if (!refreshConfig.isPaused) { + log.debug('pause auto refresh'); + await testSubjects.click('superDatePickerToggleRefreshButton'); + await this.closeQuickSelectTimeMenu(); + } + + await this.closeQuickSelectTimeMenu(); + } + + async resumeAutoRefresh() { + log.debug('resumeAutoRefresh'); + const refreshConfig = await this.getRefreshConfig(true); + if (refreshConfig.isPaused) { + log.debug('resume auto refresh'); + await testSubjects.click('superDatePickerToggleRefreshButton'); + } + + await this.closeQuickSelectTimeMenu(); + } + } + + return new TimePickerPage(); +} diff --git a/test/functional/services/apps_menu.js b/test/functional/services/apps_menu.js index 2d7e7f49cf682..d57ad68f9297f 100644 --- a/test/functional/services/apps_menu.js +++ b/test/functional/services/apps_menu.js @@ -21,7 +21,7 @@ export function AppsMenuProvider({ getService }) { const testSubjects = getService('testSubjects'); const log = getService('log'); const retry = getService('retry'); - const flyout = getService('flyout'); + const globalNav = getService('globalNav'); return new class AppsMenu { async readLinks() { @@ -42,38 +42,31 @@ export function AppsMenuProvider({ getService }) { } async clickLink(appTitle) { - log.debug(`click "${appTitle}" tab`); - await this._ensureMenuOpen(); - const container = await testSubjects.find('appsMenu'); - const link = await container.findByPartialLinkText(appTitle); - await link.click(); + try { + log.debug(`click "${appTitle}" tab`); + await this._ensureMenuOpen(); + const container = await testSubjects.find('appsMenu'); + const link = await container.findByPartialLinkText(appTitle); + await link.click(); + } finally { + await this._ensureMenuClosed(); + } } async _ensureMenuOpen() { - // some apps render flyouts that cover the global nav menu, so we make sure all flyouts are - // closed before trying to use the appsMenu - await flyout.ensureAllClosed(); - - if (!await testSubjects.exists('appsMenu')) { - await testSubjects.click('appsMenuButton'); - await retry.waitFor('apps menu displayed', async () => ( - await testSubjects.exists('appsMenu') + if (!await testSubjects.exists('navDrawer&expanded')) { + await testSubjects.moveMouseTo('navDrawer'); + await retry.waitFor('apps drawer open', async () => ( + await testSubjects.exists('navDrawer&expanded') )); } } async _ensureMenuClosed() { - const [appsMenuButtonExists, appsMenuExists] = await Promise.all([ - testSubjects.exists('appsMenuButton'), - testSubjects.exists('appsMenu') - ]); - - if (appsMenuButtonExists && appsMenuExists) { - await testSubjects.click('appsMenuButton'); - await retry.waitFor('user menu closed', async () => ( - !await testSubjects.exists('appsMenu') - )); - } + await globalNav.moveMouseToLogo(); + await retry.waitFor('apps drawer closed', async () => ( + await testSubjects.exists('navDrawer&collapsed') + )); } }; } diff --git a/test/functional/services/global_nav.js b/test/functional/services/global_nav.js index 3cfcfc4e29012..4719f8172aa3a 100644 --- a/test/functional/services/global_nav.js +++ b/test/functional/services/global_nav.js @@ -21,6 +21,10 @@ export function GlobalNavProvider({ getService }) { const testSubjects = getService('testSubjects'); return new class GlobalNav { + async moveMouseToLogo() { + await testSubjects.moveMouseTo('headerGlobalNav logo'); + } + async clickLogo() { return await testSubjects.click('headerGlobalNav logo'); } diff --git a/x-pack/package.json b/x-pack/package.json index 79ead4b403719..48d39046fbf97 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -120,7 +120,7 @@ }, "dependencies": { "@elastic/datemath": "5.0.2", - "@elastic/eui": "6.7.2", + "@elastic/eui": "6.7.4", "@elastic/node-crypto": "0.1.2", "@elastic/numeral": "2.3.2", "@kbn/babel-preset": "1.0.0", diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/waterfall_helpers/__snapshots__/waterfall_helpers.test.ts.snap b/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/waterfall_helpers/__snapshots__/waterfall_helpers.test.ts.snap index 9fd18e285d0d4..4684f18241212 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/waterfall_helpers/__snapshots__/waterfall_helpers.test.ts.snap +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/waterfall_helpers/__snapshots__/waterfall_helpers.test.ts.snap @@ -1,5 +1,29 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`waterfall_helpers getWaterfallItems should handle cyclic references 1`] = ` +Array [ + Object { + "childIds": Array [ + "a", + ], + "id": "a", + "offset": 0, + "skew": 0, + "timestamp": 10, + }, + Object { + "childIds": Array [ + "a", + ], + "id": "a", + "offset": 10, + "parentId": "a", + "skew": undefined, + "timestamp": 20, + }, +] +`; + exports[`waterfall_helpers getWaterfallItems should order items correctly 1`] = ` Array [ Object { diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers.test.ts b/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers.test.ts index 70e517d4ff365..6dcc6c032cf50 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers.test.ts +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers.test.ts @@ -98,6 +98,20 @@ describe('waterfall_helpers', () => { getWaterfallItems(childrenByParentId, entryTransactionItem) ).toMatchSnapshot(); }); + + it('should handle cyclic references', () => { + const items = [ + { id: 'a', timestamp: 10 } as IWaterfallItem, + { id: 'a', parentId: 'a', timestamp: 20 } as IWaterfallItem + ]; + const childrenByParentId = groupBy(items, hit => + hit.parentId ? hit.parentId : 'root' + ); + const entryTransactionItem = childrenByParentId.root[0]; + expect( + getWaterfallItems(childrenByParentId, entryTransactionItem) + ).toMatchSnapshot(); + }); }); describe('getClockSkew', () => { diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers.ts b/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers.ts index 9807787727448..4ecd9d8bf6678 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers.ts +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers.ts @@ -155,10 +155,15 @@ export function getWaterfallItems( childrenByParentId: IWaterfallGroup, entryTransactionItem: IWaterfallItem ) { + const visitedWaterfallItemSet = new Set(); function getSortedChildren( item: IWaterfallItem, parentItem?: IWaterfallItem ): IWaterfallItem[] { + if (visitedWaterfallItemSet.has(item)) { + return []; + } + visitedWaterfallItemSet.add(item); const children = sortBy(childrenByParentId[item.id] || [], 'timestamp'); item.childIds = children.map(child => child.id); diff --git a/x-pack/plugins/apm/public/components/shared/charts/CustomPlot/Legends.js b/x-pack/plugins/apm/public/components/shared/charts/CustomPlot/Legends.js index 440f93ef409e1..7d2f3a1b7e04d 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/CustomPlot/Legends.js +++ b/x-pack/plugins/apm/public/components/shared/charts/CustomPlot/Legends.js @@ -19,8 +19,7 @@ import { const Container = styled.div` display: flex; - align-items: center; - justify-content: space-around; + margin-left: ${px(unit * 5)}; flex-wrap: wrap; /* add margin to all direct descendant divs */ diff --git a/x-pack/plugins/apm/public/components/shared/charts/CustomPlot/test/__snapshots__/CustomPlot.test.js.snap b/x-pack/plugins/apm/public/components/shared/charts/CustomPlot/test/__snapshots__/CustomPlot.test.js.snap index c5bbba6b8c830..37dfb61f95f8f 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/CustomPlot/test/__snapshots__/CustomPlot.test.js.snap +++ b/x-pack/plugins/apm/public/components/shared/charts/CustomPlot/test/__snapshots__/CustomPlot.test.js.snap @@ -2673,14 +2673,7 @@ Array [ display: -webkit-flex; display: -ms-flexbox; display: flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - -webkit-box-pack: space-around; - -webkit-justify-content: space-around; - -ms-flex-pack: space-around; - justify-content: space-around; + margin-left: 80px; -webkit-flex-wrap: wrap; -ms-flex-wrap: wrap; flex-wrap: wrap; @@ -5716,14 +5709,7 @@ Array [ display: -webkit-flex; display: -ms-flexbox; display: flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - -webkit-box-pack: space-around; - -webkit-justify-content: space-around; - -ms-flex-pack: space-around; - justify-content: space-around; + margin-left: 80px; -webkit-flex-wrap: wrap; -ms-flex-wrap: wrap; flex-wrap: wrap; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/image_upload/forms/file.js b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/image_upload/forms/file.js index af8dc1cf37d13..c51c949568dc2 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/image_upload/forms/file.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/image_upload/forms/file.js @@ -5,11 +5,21 @@ */ import React from 'react'; import PropTypes from 'prop-types'; +import { EuiFilePicker } from '@elastic/eui'; import { Loading } from '../../../../../public/components/loading/loading'; -import { FileUpload } from '../../../../../public/components/file_upload'; -export const FileForm = ({ loading, onUpload }) => - loading ? : ; +export const FileForm = ({ loading, onChange }) => + loading ? ( + + ) : ( + + ); FileForm.propTypes = { loading: PropTypes.bool, diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/image_upload/index.js b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/image_upload/index.js index d40182a1121cd..e667169b99815 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/image_upload/index.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/image_upload/index.js @@ -7,6 +7,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { EuiSpacer, EuiButtonGroup } from '@elastic/eui'; +import { get } from 'lodash'; import { AssetPicker } from '../../../../public/components/asset_picker'; import { elasticOutline } from '../../../lib/elastic_outline'; import { resolveFromArgs } from '../../../../common/lib/resolve_dataurl'; @@ -14,6 +15,7 @@ import { isValidHttpUrl } from '../../../../common/lib/httpurl'; import { encode } from '../../../../common/lib/dataurl'; import { templateFromReactComponent } from '../../../../public/lib/template_from_react_component'; import './image_upload.scss'; +import { VALID_IMAGE_TYPES } from '../../../../common/lib/constants'; import { FileForm, LinkForm } from './forms'; class ImageUpload extends React.Component { @@ -71,17 +73,21 @@ class ImageUpload extends React.Component { handleUpload = files => { const { onAssetAdd } = this.props; - const [upload] = files; - this.setState({ loading: true }); // start loading indicator + const [file] = files; - encode(upload) - .then(dataurl => onAssetAdd('dataurl', dataurl)) - .then(assetId => { - this.updateAST(assetId); + const [type, subtype] = get(file, 'type', '').split('/'); + if (type === 'image' && VALID_IMAGE_TYPES.indexOf(subtype) >= 0) { + this.setState({ loading: true }); // start loading indicator - // this component can go away when onValueChange is called, check for _isMounted - this._isMounted && this.setState({ loading: false }); // set loading state back to false - }); + encode(file) + .then(dataurl => onAssetAdd('dataurl', dataurl)) + .then(assetId => { + this.updateAST(assetId); + + // this component can go away when onValueChange is called, check for _isMounted + this._isMounted && this.setState({ loading: false }); // set loading state back to false + }); + } }; changeUrlType = optionId => { @@ -119,7 +125,7 @@ class ImageUpload extends React.Component { ); const forms = { - file: , + file: , link: ( this.setState({ isModalVisible: true }); closeModal = () => this.setState({ isModalVisible: false }); @@ -52,6 +59,13 @@ export class AssetManager extends React.PureComponent { this.props.removeAsset(this.state.deleteId); }; + handleFileUpload = files => { + this.setState({ loading: true }); + Promise.all(Array.from(files).map(file => this.props.onAssetAdd(file))).finally(() => { + this._isMounted && this.setState({ loading: false }); + }); + }; + addElement = assetId => { this.props.addImageElement(assetId); }; @@ -132,16 +146,32 @@ export class AssetManager extends React.PureComponent { ); render() { - const { isModalVisible } = this.state; + const { isModalVisible, loading } = this.state; + const { assets } = this.props; const assetMaxLimit = 25000; const assetsTotal = Math.round( - this.props.assets.reduce((total, asset) => total + asset.value.length, 0) / 1024 + assets.reduce((total, asset) => total + asset.value.length, 0) / 1024 ); const percentageUsed = Math.round((assetsTotal / assetMaxLimit) * 100); + const emptyAssets = ( + + No available assets} + titleSize="s" + body={ + +

Upload your assets above to get started

+
+ } + /> +
+ ); + const assetModal = isModalVisible ? ( Manage workpad assets + + + {loading ? ( + + ) : ( + + )} + + + + + +

+ Below are the image assets that you added to this workpad. To reclaim space, delete + assets that you no longer need. Unfortunately, any assets that are actually in use + cannot be determined at this time. +

+
+ + {assets.length ? ( + + {assets.map(this.renderAsset)} + + ) : ( + emptyAssets + )} +
+ {percentageUsed}% space used - - - -

- Below are the image assets that you added to this workpad. To reclaim space, delete - assets that you no longer need. Unfortunately, any assets that are actually in use - cannot be determined at this time. -

-
- - {this.props.assets.map(this.renderAsset)} - -
- Close diff --git a/x-pack/plugins/canvas/public/components/asset_manager/asset_manager.scss b/x-pack/plugins/canvas/public/components/asset_manager/asset_manager.scss index 80ab585c963c2..84534971d0ec5 100644 --- a/x-pack/plugins/canvas/public/components/asset_manager/asset_manager.scss +++ b/x-pack/plugins/canvas/public/components/asset_manager/asset_manager.scss @@ -1,5 +1,4 @@ .canvasAssetManager { - .canvasAssetManager__modalHeader { flex-wrap: wrap; } @@ -15,8 +14,6 @@ flex-grow: 0; min-width: 40%; align-items: center; - justify-content: flex-end; - padding-right: $euiSize; @include euiBreakpoint('xs', 's') { flex-grow: 1; @@ -27,6 +24,11 @@ margin: 0; } + .canvasAssetManager__fileUploadWrapper { + justify-content: flex-end; + padding-right: $euiSize; + } + // ASSETS LIST .canvasAssetManager__asset { @@ -34,6 +36,11 @@ overflow: hidden; // hides image from outer panel boundaries } + .canvasAssetManager__emptyPanel { + max-width: 400px; + margin: 0 auto; + } + .canvasAssetManager__thumb { margin: -$euiSizeS; margin-bottom: 0; @@ -52,4 +59,8 @@ opacity: 0; // only show the background image (which will properly keep proportions) } } + + .canvasAssetManager__modalFooter { + justify-content: space-between; + } } diff --git a/x-pack/plugins/canvas/public/components/asset_manager/index.js b/x-pack/plugins/canvas/public/components/asset_manager/index.js index 2417e572e1a02..878928a452c56 100644 --- a/x-pack/plugins/canvas/public/components/asset_manager/index.js +++ b/x-pack/plugins/canvas/public/components/asset_manager/index.js @@ -6,14 +6,18 @@ import { connect } from 'react-redux'; import { compose, withProps } from 'recompose'; -import { set } from 'lodash'; +import { set, get } from 'lodash'; import { fromExpression, toExpression } from '@kbn/interpreter/common'; import { notify } from '../../lib/notify'; import { getAssets } from '../../state/selectors/assets'; -import { removeAsset } from '../../state/actions/assets'; +import { removeAsset, createAsset } from '../../state/actions/assets'; import { elementsRegistry } from '../../lib/elements_registry'; import { addElement } from '../../state/actions/elements'; import { getSelectedPage } from '../../state/selectors/workpad'; +import { encode } from '../../../common/lib/dataurl'; +import { getId } from '../../lib/get_id'; +import { findExistingAsset } from '../../lib/find_existing_asset'; +import { VALID_IMAGE_TYPES } from '../../../common/lib/constants'; import { AssetManager as Component } from './asset_manager'; const mapStateToProps = state => ({ @@ -44,15 +48,40 @@ const mapDispatchToProps = dispatch => ({ imageElement.expression = toExpression(newAST); dispatch(addElement(pageId, imageElement)); }, + onAssetAdd: (type, content) => { + // make the ID here and pass it into the action + const assetId = getId('asset'); + dispatch(createAsset(type, content, assetId)); + + // then return the id, so the caller knows the id that will be created + return assetId; + }, removeAsset: assetId => dispatch(removeAsset(assetId)), }); const mergeProps = (stateProps, dispatchProps, ownProps) => { + const { assets } = stateProps; + const { onAssetAdd } = dispatchProps; return { ...ownProps, ...stateProps, ...dispatchProps, addImageElement: dispatchProps.addImageElement(stateProps.selectedPage), + onAssetAdd: file => { + const [type, subtype] = get(file, 'type', '').split('/'); + if (type === 'image' && VALID_IMAGE_TYPES.indexOf(subtype) >= 0) { + return encode(file).then(dataurl => { + const type = 'dataurl'; + const existingId = findExistingAsset(type, dataurl, assets); + if (existingId) { + return existingId; + } + return onAssetAdd(type, dataurl); + }); + } + + return false; + }, }; }; diff --git a/x-pack/plugins/canvas/public/components/file_upload/file_upload.js b/x-pack/plugins/canvas/public/components/file_upload/file_upload.js deleted file mode 100644 index 9640ab01bd158..0000000000000 --- a/x-pack/plugins/canvas/public/components/file_upload/file_upload.js +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import PropTypes from 'prop-types'; -import { EuiFilePicker } from '@elastic/eui'; - -export const FileUpload = ({ id = '', className = 'canvasFileUpload', onUpload }) => ( - -); - -FileUpload.propTypes = { - id: PropTypes.string, - className: PropTypes.string, - onUpload: PropTypes.func.isRequired, -}; diff --git a/x-pack/plugins/canvas/public/components/function_form/index.js b/x-pack/plugins/canvas/public/components/function_form/index.js index fbde896361cb6..5ca12b26e48c7 100644 --- a/x-pack/plugins/canvas/public/components/function_form/index.js +++ b/x-pack/plugins/canvas/public/components/function_form/index.js @@ -21,6 +21,7 @@ import { getContextForIndex, } from '../../state/selectors/workpad'; import { getAssets } from '../../state/selectors/assets'; +import { findExistingAsset } from '../../lib/find_existing_asset'; import { FunctionForm as Component } from './function_form'; const mapStateToProps = (state, { expressionIndex }) => ({ @@ -93,9 +94,7 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => { onValueAdd: addArgument(element, pageId), onValueRemove: deleteArgument(element, pageId), onAssetAdd: (type, content) => { - const existingId = Object.keys(assets).find( - assetId => assets[assetId].type === type && assets[assetId].value === content - ); + const existingId = findExistingAsset(type, content, assets); if (existingId) { return existingId; } diff --git a/x-pack/plugins/canvas/public/components/workpad_header/index.js b/x-pack/plugins/canvas/public/components/workpad_header/index.js index fec9c874744f3..e724b97b0bb4f 100644 --- a/x-pack/plugins/canvas/public/components/workpad_header/index.js +++ b/x-pack/plugins/canvas/public/components/workpad_header/index.js @@ -9,7 +9,6 @@ import { connect } from 'react-redux'; import { canUserWrite } from '../../state/selectors/app'; import { getWorkpadName, getSelectedPage, isWriteable } from '../../state/selectors/workpad'; import { setWriteable } from '../../state/actions/workpad'; -import { getAssets } from '../../state/selectors/assets'; import { addElement } from '../../state/actions/elements'; import { WorkpadHeader as Component } from './workpad_header'; @@ -18,7 +17,6 @@ const mapStateToProps = state => ({ canUserWrite: canUserWrite(state), workpadName: getWorkpadName(state), selectedPage: getSelectedPage(state), - hasAssets: Object.keys(getAssets(state)).length ? true : false, }); const mapDispatchToProps = dispatch => ({ diff --git a/x-pack/plugins/canvas/public/components/workpad_header/workpad_header.js b/x-pack/plugins/canvas/public/components/workpad_header/workpad_header.js index c4fa1ce706229..88f92f7eb69e6 100644 --- a/x-pack/plugins/canvas/public/components/workpad_header/workpad_header.js +++ b/x-pack/plugins/canvas/public/components/workpad_header/workpad_header.js @@ -27,7 +27,6 @@ export const WorkpadHeader = ({ isWriteable, canUserWrite, toggleWriteable, - hasAssets, addElement, setShowElementModal, showElementModal, @@ -115,11 +114,9 @@ export const WorkpadHeader = ({ {isWriteable ? ( - {hasAssets && ( - - - - )} + + + date && moment(date).format('MMM D, YYYY @ h:mma'); @@ -79,7 +80,7 @@ export class WorkpadLoader extends React.PureComponent { }; // create new workpad from uploaded JSON - uploadWorkpad = async workpad => { + onUpload = async workpad => { this.setState({ createPending: true }); await this.props.createWorkpad(workpad); this._isMounted && this.setState({ createPending: false }); @@ -232,7 +233,7 @@ export class WorkpadLoader extends React.PureComponent { return ( - + + uploadWorkpad(file, this.onUpload)} + accept="application/json" + disabled={createPending || !canUserWrite} + /> ); if (!canUserWrite) { diff --git a/x-pack/plugins/canvas/public/components/workpad_loader/workpad_upload.js b/x-pack/plugins/canvas/public/components/workpad_loader/workpad_upload.js deleted file mode 100644 index 9c4372175b89f..0000000000000 --- a/x-pack/plugins/canvas/public/components/workpad_loader/workpad_upload.js +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import PropTypes from 'prop-types'; -import { EuiFilePicker } from '@elastic/eui'; -import { uploadWorkpad } from './upload_workpad'; - -export const WorkpadUpload = ({ onUpload, ...rest }) => ( - uploadWorkpad(file, onUpload)} - /> -); - -WorkpadUpload.propTypes = { - onUpload: PropTypes.func.isRequired, -}; diff --git a/x-pack/plugins/canvas/public/lib/find_existing_asset.js b/x-pack/plugins/canvas/public/lib/find_existing_asset.js new file mode 100644 index 0000000000000..470d7797e6feb --- /dev/null +++ b/x-pack/plugins/canvas/public/lib/find_existing_asset.js @@ -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. + */ + +export const findExistingAsset = (type, content, assets) => { + const existingId = Object.keys(assets).find( + assetId => assets[assetId].type === type && assets[assetId].value === content + ); + return existingId; +}; diff --git a/x-pack/plugins/canvas/public/state/middleware/aeroelastic.js b/x-pack/plugins/canvas/public/state/middleware/aeroelastic.js index ac69230861f3e..052df02d43e8c 100644 --- a/x-pack/plugins/canvas/public/state/middleware/aeroelastic.js +++ b/x-pack/plugins/canvas/public/state/middleware/aeroelastic.js @@ -344,7 +344,10 @@ export const aeroelastic = ({ dispatch, getState }) => { populateWithElements(page); } - if (action.type !== setMultiplePositions.toString()) { + if ( + action.type !== setMultiplePositions.toString() && + action.type !== elementLayer.toString() + ) { unselectShape(prevPage); } diff --git a/x-pack/plugins/console_extensions/spec/generated/ccr.follow.json b/x-pack/plugins/console_extensions/spec/generated/ccr.follow.json index 704cb082161ae..ba993d25c60ec 100644 --- a/x-pack/plugins/console_extensions/spec/generated/ccr.follow.json +++ b/x-pack/plugins/console_extensions/spec/generated/ccr.follow.json @@ -4,7 +4,7 @@ "PUT" ], "patterns": [ - "{index}/_ccr/follow" + "{indices}/_ccr/follow" ], "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/current/ccr-put-follow.html" } diff --git a/x-pack/plugins/console_extensions/spec/generated/ccr.follow_stats.json b/x-pack/plugins/console_extensions/spec/generated/ccr.follow_stats.json index 47553262c95d2..6d72e12df17d4 100644 --- a/x-pack/plugins/console_extensions/spec/generated/ccr.follow_stats.json +++ b/x-pack/plugins/console_extensions/spec/generated/ccr.follow_stats.json @@ -4,7 +4,7 @@ "GET" ], "patterns": [ - "{index}/_ccr/stats" + "{indices}/_ccr/stats" ], "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/current/ccr-get-follow-stats.html" } diff --git a/x-pack/plugins/console_extensions/spec/generated/ccr.pause_follow.json b/x-pack/plugins/console_extensions/spec/generated/ccr.pause_follow.json index 4dcb0fdaf9b87..72afa796e1964 100644 --- a/x-pack/plugins/console_extensions/spec/generated/ccr.pause_follow.json +++ b/x-pack/plugins/console_extensions/spec/generated/ccr.pause_follow.json @@ -4,7 +4,7 @@ "POST" ], "patterns": [ - "{index}/_ccr/pause_follow" + "{indices}/_ccr/pause_follow" ], "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/current/ccr-post-pause-follow.html" } diff --git a/x-pack/plugins/console_extensions/spec/generated/ccr.resume_follow.json b/x-pack/plugins/console_extensions/spec/generated/ccr.resume_follow.json index 00b889d0d5f9a..3710cdb8cc577 100644 --- a/x-pack/plugins/console_extensions/spec/generated/ccr.resume_follow.json +++ b/x-pack/plugins/console_extensions/spec/generated/ccr.resume_follow.json @@ -4,7 +4,7 @@ "POST" ], "patterns": [ - "{index}/_ccr/resume_follow" + "{indices}/_ccr/resume_follow" ], "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/current/ccr-post-resume-follow.html" } diff --git a/x-pack/plugins/console_extensions/spec/generated/ccr.unfollow.json b/x-pack/plugins/console_extensions/spec/generated/ccr.unfollow.json index 9d9d6868a2fc9..92759d8222c63 100644 --- a/x-pack/plugins/console_extensions/spec/generated/ccr.unfollow.json +++ b/x-pack/plugins/console_extensions/spec/generated/ccr.unfollow.json @@ -4,8 +4,8 @@ "POST" ], "patterns": [ - "{index}/_ccr/unfollow" + "{indices}/_ccr/unfollow" ], - "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/current/ccr-post-unfollow.html" + "documentation": "http://www.elastic.co/guide/en/elasticsearch/reference/current" } } diff --git a/x-pack/plugins/console_extensions/spec/generated/ilm.remove_policy.json b/x-pack/plugins/console_extensions/spec/generated/ilm.remove_policy.json index fc835953c2c6d..5a3c760386f10 100644 --- a/x-pack/plugins/console_extensions/spec/generated/ilm.remove_policy.json +++ b/x-pack/plugins/console_extensions/spec/generated/ilm.remove_policy.json @@ -4,8 +4,7 @@ "POST" ], "patterns": [ - "{indices}/_ilm/remove", - "_ilm/remove" + "{indices}/_ilm/remove" ], "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/current/ilm-remove-policy.html" } diff --git a/x-pack/plugins/console_extensions/spec/generated/indices.freeze.json b/x-pack/plugins/console_extensions/spec/generated/indices.freeze.json new file mode 100644 index 0000000000000..06e8c606f59f7 --- /dev/null +++ b/x-pack/plugins/console_extensions/spec/generated/indices.freeze.json @@ -0,0 +1,24 @@ +{ + "indices.freeze": { + "url_params": { + "timeout": "", + "master_timeout": "", + "ignore_unavailable": "__flag__", + "allow_no_indices": "__flag__", + "expand_wildcards": [ + "open", + "closed", + "none", + "all" + ], + "wait_for_active_shards": "" + }, + "methods": [ + "POST" + ], + "patterns": [ + "{indices}/_freeze" + ], + "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/current/frozen.html" + } +} diff --git a/x-pack/plugins/console_extensions/spec/generated/indices.unfreeze.json b/x-pack/plugins/console_extensions/spec/generated/indices.unfreeze.json new file mode 100644 index 0000000000000..186a671347240 --- /dev/null +++ b/x-pack/plugins/console_extensions/spec/generated/indices.unfreeze.json @@ -0,0 +1,24 @@ +{ + "indices.unfreeze": { + "url_params": { + "timeout": "", + "master_timeout": "", + "ignore_unavailable": "__flag__", + "allow_no_indices": "__flag__", + "expand_wildcards": [ + "open", + "closed", + "none", + "all" + ], + "wait_for_active_shards": "" + }, + "methods": [ + "POST" + ], + "patterns": [ + "{indices}/_unfreeze" + ], + "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/current/frozen.html" + } +} diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.close_job.json b/x-pack/plugins/console_extensions/spec/generated/ml.close_job.json similarity index 78% rename from x-pack/plugins/console_extensions/spec/generated/xpack.ml.close_job.json rename to x-pack/plugins/console_extensions/spec/generated/ml.close_job.json index 933f79a1028ed..310b0d125b1f9 100644 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.close_job.json +++ b/x-pack/plugins/console_extensions/spec/generated/ml.close_job.json @@ -1,5 +1,5 @@ { - "xpack.ml.close_job": { + "ml.close_job": { "url_params": { "allow_no_jobs": "__flag__", "force": "__flag__", @@ -9,7 +9,7 @@ "POST" ], "patterns": [ - "_xpack/ml/anomaly_detectors/{job_id}/_close" + "_ml/anomaly_detectors/{job_id}/_close" ], "documentation": "http://www.elastic.co/guide/en/elasticsearch/reference/current/ml-close-job.html" } diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.delete_filter.json b/x-pack/plugins/console_extensions/spec/generated/ml.delete_calendar.json similarity index 51% rename from x-pack/plugins/console_extensions/spec/generated/xpack.ml.delete_filter.json rename to x-pack/plugins/console_extensions/spec/generated/ml.delete_calendar.json index 5a4a8cd232448..97e5898072a08 100644 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.delete_filter.json +++ b/x-pack/plugins/console_extensions/spec/generated/ml.delete_calendar.json @@ -1,10 +1,10 @@ { - "xpack.ml.delete_filter": { + "ml.delete_calendar": { "methods": [ "DELETE" ], "patterns": [ - "_xpack/ml/filters/{filter_id}" + "_ml/calendars/{calendar_id}" ] } } diff --git a/x-pack/plugins/console_extensions/spec/generated/ml.delete_calendar_event.json b/x-pack/plugins/console_extensions/spec/generated/ml.delete_calendar_event.json new file mode 100644 index 0000000000000..85ea5add0b60b --- /dev/null +++ b/x-pack/plugins/console_extensions/spec/generated/ml.delete_calendar_event.json @@ -0,0 +1,10 @@ +{ + "ml.delete_calendar_event": { + "methods": [ + "DELETE" + ], + "patterns": [ + "_ml/calendars/{calendar_id}/events/{event_id}" + ] + } +} diff --git a/x-pack/plugins/console_extensions/spec/generated/ml.delete_calendar_job.json b/x-pack/plugins/console_extensions/spec/generated/ml.delete_calendar_job.json new file mode 100644 index 0000000000000..6665f52eeb90d --- /dev/null +++ b/x-pack/plugins/console_extensions/spec/generated/ml.delete_calendar_job.json @@ -0,0 +1,10 @@ +{ + "ml.delete_calendar_job": { + "methods": [ + "DELETE" + ], + "patterns": [ + "_ml/calendars/{calendar_id}/jobs/{job_id}" + ] + } +} diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.delete_datafeed.json b/x-pack/plugins/console_extensions/spec/generated/ml.delete_datafeed.json similarity index 76% rename from x-pack/plugins/console_extensions/spec/generated/xpack.ml.delete_datafeed.json rename to x-pack/plugins/console_extensions/spec/generated/ml.delete_datafeed.json index af1d5564cf9ca..7c7f3c40f23bb 100644 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.delete_datafeed.json +++ b/x-pack/plugins/console_extensions/spec/generated/ml.delete_datafeed.json @@ -1,5 +1,5 @@ { - "xpack.ml.delete_datafeed": { + "ml.delete_datafeed": { "url_params": { "force": "__flag__" }, @@ -7,7 +7,7 @@ "DELETE" ], "patterns": [ - "_xpack/ml/datafeeds/{datafeed_id}" + "_ml/datafeeds/{datafeed_id}" ], "documentation": "http://www.elastic.co/guide/en/elasticsearch/reference/current/ml-delete-datafeed.html" } diff --git a/x-pack/plugins/console_extensions/spec/generated/ml.delete_expired_data.json b/x-pack/plugins/console_extensions/spec/generated/ml.delete_expired_data.json new file mode 100644 index 0000000000000..4afa9e323b030 --- /dev/null +++ b/x-pack/plugins/console_extensions/spec/generated/ml.delete_expired_data.json @@ -0,0 +1,10 @@ +{ + "ml.delete_expired_data": { + "methods": [ + "DELETE" + ], + "patterns": [ + "_ml/_delete_expired_data" + ] + } +} diff --git a/x-pack/plugins/console_extensions/spec/generated/ml.delete_filter.json b/x-pack/plugins/console_extensions/spec/generated/ml.delete_filter.json new file mode 100644 index 0000000000000..8210a2acd71d0 --- /dev/null +++ b/x-pack/plugins/console_extensions/spec/generated/ml.delete_filter.json @@ -0,0 +1,10 @@ +{ + "ml.delete_filter": { + "methods": [ + "DELETE" + ], + "patterns": [ + "_ml/filters/{filter_id}" + ] + } +} diff --git a/x-pack/plugins/console_extensions/spec/generated/ml.delete_forecast.json b/x-pack/plugins/console_extensions/spec/generated/ml.delete_forecast.json new file mode 100644 index 0000000000000..971a761cc77e9 --- /dev/null +++ b/x-pack/plugins/console_extensions/spec/generated/ml.delete_forecast.json @@ -0,0 +1,16 @@ +{ + "ml.delete_forecast": { + "url_params": { + "allow_no_forecasts": "__flag__", + "timeout": "" + }, + "methods": [ + "DELETE" + ], + "patterns": [ + "_ml/anomaly_detectors/{job_id}/_forecast", + "_ml/anomaly_detectors/{job_id}/_forecast/{forecast_id}" + ], + "documentation": "http://www.elastic.co/guide/en/elasticsearch/reference/current/ml-delete-forecast.html" + } +} diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.delete_job.json b/x-pack/plugins/console_extensions/spec/generated/ml.delete_job.json similarity index 61% rename from x-pack/plugins/console_extensions/spec/generated/xpack.ml.delete_job.json rename to x-pack/plugins/console_extensions/spec/generated/ml.delete_job.json index 26c45ad0fc6fb..ab518071bf765 100644 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.delete_job.json +++ b/x-pack/plugins/console_extensions/spec/generated/ml.delete_job.json @@ -1,13 +1,14 @@ { - "xpack.ml.delete_job": { + "ml.delete_job": { "url_params": { - "force": "__flag__" + "force": "__flag__", + "wait_for_completion": "__flag__" }, "methods": [ "DELETE" ], "patterns": [ - "_xpack/ml/anomaly_detectors/{job_id}" + "_ml/anomaly_detectors/{job_id}" ], "documentation": "http://www.elastic.co/guide/en/elasticsearch/reference/current/ml-delete-job.html" } diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.delete_model_snapshot.json b/x-pack/plugins/console_extensions/spec/generated/ml.delete_model_snapshot.json similarity index 61% rename from x-pack/plugins/console_extensions/spec/generated/xpack.ml.delete_model_snapshot.json rename to x-pack/plugins/console_extensions/spec/generated/ml.delete_model_snapshot.json index faf9da66f3bee..53d45bf0498ab 100644 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.delete_model_snapshot.json +++ b/x-pack/plugins/console_extensions/spec/generated/ml.delete_model_snapshot.json @@ -1,10 +1,10 @@ { - "xpack.ml.delete_model_snapshot": { + "ml.delete_model_snapshot": { "methods": [ "DELETE" ], "patterns": [ - "_xpack/ml/anomaly_detectors/{job_id}/model_snapshots/{snapshot_id}" + "_ml/anomaly_detectors/{job_id}/model_snapshots/{snapshot_id}" ], "documentation": "http://www.elastic.co/guide/en/elasticsearch/reference/current/ml-delete-snapshot.html" } diff --git a/x-pack/plugins/console_extensions/spec/generated/ml.find_file_structure.json b/x-pack/plugins/console_extensions/spec/generated/ml.find_file_structure.json new file mode 100644 index 0000000000000..93706ea628a28 --- /dev/null +++ b/x-pack/plugins/console_extensions/spec/generated/ml.find_file_structure.json @@ -0,0 +1,31 @@ +{ + "ml.find_file_structure": { + "url_params": { + "lines_to_sample": 0, + "timeout": "", + "charset": "", + "format": [ + "ndjson", + "xml", + "delimited", + "semi_structured_text" + ], + "has_header_row": "__flag__", + "column_names": [], + "delimiter": "", + "quote": "", + "should_trim_fields": "__flag__", + "grok_pattern": "", + "timestamp_field": "", + "timestamp_format": "", + "explain": "__flag__" + }, + "methods": [ + "POST" + ], + "patterns": [ + "_ml/find_file_structure" + ], + "documentation": "http://www.elastic.co/guide/en/elasticsearch/reference/current/ml-file-structure.html" + } +} diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.flush_job.json b/x-pack/plugins/console_extensions/spec/generated/ml.flush_job.json similarity index 80% rename from x-pack/plugins/console_extensions/spec/generated/xpack.ml.flush_job.json rename to x-pack/plugins/console_extensions/spec/generated/ml.flush_job.json index 190fa9813384e..2f496003a2834 100644 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.flush_job.json +++ b/x-pack/plugins/console_extensions/spec/generated/ml.flush_job.json @@ -1,5 +1,5 @@ { - "xpack.ml.flush_job": { + "ml.flush_job": { "url_params": { "calc_interim": "__flag__", "start": "", @@ -11,7 +11,7 @@ "POST" ], "patterns": [ - "_xpack/ml/anomaly_detectors/{job_id}/_flush" + "_ml/anomaly_detectors/{job_id}/_flush" ], "documentation": "http://www.elastic.co/guide/en/elasticsearch/reference/current/ml-flush-job.html" } diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.forecast.json b/x-pack/plugins/console_extensions/spec/generated/ml.forecast.json similarity index 63% rename from x-pack/plugins/console_extensions/spec/generated/xpack.ml.forecast.json rename to x-pack/plugins/console_extensions/spec/generated/ml.forecast.json index e96a5100e98dd..3a8849aad3e4d 100644 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.forecast.json +++ b/x-pack/plugins/console_extensions/spec/generated/ml.forecast.json @@ -1,5 +1,5 @@ { - "xpack.ml.forecast": { + "ml.forecast": { "url_params": { "duration": "", "expires_in": "" @@ -8,7 +8,7 @@ "POST" ], "patterns": [ - "_xpack/ml/anomaly_detectors/{job_id}/_forecast" + "_ml/anomaly_detectors/{job_id}/_forecast" ] } } diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.get_buckets.json b/x-pack/plugins/console_extensions/spec/generated/ml.get_buckets.json similarity index 71% rename from x-pack/plugins/console_extensions/spec/generated/xpack.ml.get_buckets.json rename to x-pack/plugins/console_extensions/spec/generated/ml.get_buckets.json index a63c47ab52429..2cbcb9d6155ec 100644 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.get_buckets.json +++ b/x-pack/plugins/console_extensions/spec/generated/ml.get_buckets.json @@ -1,5 +1,5 @@ { - "xpack.ml.get_buckets": { + "ml.get_buckets": { "url_params": { "expand": "__flag__", "exclude_interim": "__flag__", @@ -16,8 +16,8 @@ "POST" ], "patterns": [ - "_xpack/ml/anomaly_detectors/{job_id}/results/buckets/{timestamp}", - "_xpack/ml/anomaly_detectors/{job_id}/results/buckets" + "_ml/anomaly_detectors/{job_id}/results/buckets/{timestamp}", + "_ml/anomaly_detectors/{job_id}/results/buckets" ], "documentation": "http://www.elastic.co/guide/en/elasticsearch/reference/current/ml-get-bucket.html" } diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.get_calendar_events.json b/x-pack/plugins/console_extensions/spec/generated/ml.get_calendar_events.json similarity index 68% rename from x-pack/plugins/console_extensions/spec/generated/xpack.ml.get_calendar_events.json rename to x-pack/plugins/console_extensions/spec/generated/ml.get_calendar_events.json index c6734d6cdc893..8999af6320dfa 100644 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.get_calendar_events.json +++ b/x-pack/plugins/console_extensions/spec/generated/ml.get_calendar_events.json @@ -1,5 +1,5 @@ { - "xpack.ml.get_calendar_events": { + "ml.get_calendar_events": { "url_params": { "job_id": "", "start": "", @@ -11,7 +11,7 @@ "GET" ], "patterns": [ - "_xpack/ml/calendars/{calendar_id}/events" + "_ml/calendars/{calendar_id}/events" ] } } diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.get_calendars.json b/x-pack/plugins/console_extensions/spec/generated/ml.get_calendars.json similarity index 58% rename from x-pack/plugins/console_extensions/spec/generated/xpack.ml.get_calendars.json rename to x-pack/plugins/console_extensions/spec/generated/ml.get_calendars.json index 0b014b7f000e5..87f582b5c364d 100644 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.get_calendars.json +++ b/x-pack/plugins/console_extensions/spec/generated/ml.get_calendars.json @@ -1,5 +1,5 @@ { - "xpack.ml.get_calendars": { + "ml.get_calendars": { "url_params": { "from": 0, "size": 0 @@ -9,8 +9,8 @@ "POST" ], "patterns": [ - "_xpack/ml/calendars", - "_xpack/ml/calendars/{calendar_id}" + "_ml/calendars", + "_ml/calendars/{calendar_id}" ] } } diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.get_categories.json b/x-pack/plugins/console_extensions/spec/generated/ml.get_categories.json similarity index 58% rename from x-pack/plugins/console_extensions/spec/generated/xpack.ml.get_categories.json rename to x-pack/plugins/console_extensions/spec/generated/ml.get_categories.json index 1157a0a375880..357a7b7fb0ccc 100644 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.get_categories.json +++ b/x-pack/plugins/console_extensions/spec/generated/ml.get_categories.json @@ -1,5 +1,5 @@ { - "xpack.ml.get_categories": { + "ml.get_categories": { "url_params": { "from": 0, "size": 0 @@ -9,8 +9,8 @@ "POST" ], "patterns": [ - "_xpack/ml/anomaly_detectors/{job_id}/results/categories/{category_id}", - "_xpack/ml/anomaly_detectors/{job_id}/results/categories/" + "_ml/anomaly_detectors/{job_id}/results/categories/{category_id}", + "_ml/anomaly_detectors/{job_id}/results/categories/" ], "documentation": "http://www.elastic.co/guide/en/elasticsearch/reference/current/ml-get-category.html" } diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.get_datafeed_stats.json b/x-pack/plugins/console_extensions/spec/generated/ml.get_datafeed_stats.json similarity index 67% rename from x-pack/plugins/console_extensions/spec/generated/xpack.ml.get_datafeed_stats.json rename to x-pack/plugins/console_extensions/spec/generated/ml.get_datafeed_stats.json index 8b5613da5c935..5c300e444c794 100644 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.get_datafeed_stats.json +++ b/x-pack/plugins/console_extensions/spec/generated/ml.get_datafeed_stats.json @@ -1,5 +1,5 @@ { - "xpack.ml.get_datafeed_stats": { + "ml.get_datafeed_stats": { "url_params": { "allow_no_datafeeds": "__flag__" }, @@ -7,8 +7,8 @@ "GET" ], "patterns": [ - "_xpack/ml/datafeeds/{datafeed_id}/_stats", - "_xpack/ml/datafeeds/_stats" + "_ml/datafeeds/{datafeed_id}/_stats", + "_ml/datafeeds/_stats" ], "documentation": "http://www.elastic.co/guide/en/elasticsearch/reference/current/ml-get-datafeed-stats.html" } diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.get_datafeeds.json b/x-pack/plugins/console_extensions/spec/generated/ml.get_datafeeds.json similarity index 70% rename from x-pack/plugins/console_extensions/spec/generated/xpack.ml.get_datafeeds.json rename to x-pack/plugins/console_extensions/spec/generated/ml.get_datafeeds.json index 1f44b4aba572a..9979a685426be 100644 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.get_datafeeds.json +++ b/x-pack/plugins/console_extensions/spec/generated/ml.get_datafeeds.json @@ -1,5 +1,5 @@ { - "xpack.ml.get_datafeeds": { + "ml.get_datafeeds": { "url_params": { "allow_no_datafeeds": "__flag__" }, @@ -7,8 +7,8 @@ "GET" ], "patterns": [ - "_xpack/ml/datafeeds/{datafeed_id}", - "_xpack/ml/datafeeds" + "_ml/datafeeds/{datafeed_id}", + "_ml/datafeeds" ], "documentation": "http://www.elastic.co/guide/en/elasticsearch/reference/current/ml-get-datafeed.html" } diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.get_filters.json b/x-pack/plugins/console_extensions/spec/generated/ml.get_filters.json similarity index 57% rename from x-pack/plugins/console_extensions/spec/generated/xpack.ml.get_filters.json rename to x-pack/plugins/console_extensions/spec/generated/ml.get_filters.json index 0e33294934ba4..9b06e618a0b0c 100644 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.get_filters.json +++ b/x-pack/plugins/console_extensions/spec/generated/ml.get_filters.json @@ -1,5 +1,5 @@ { - "xpack.ml.get_filters": { + "ml.get_filters": { "url_params": { "from": 0, "size": 0 @@ -8,8 +8,8 @@ "GET" ], "patterns": [ - "_xpack/ml/filters", - "_xpack/ml/filters/{filter_id}" + "_ml/filters", + "_ml/filters/{filter_id}" ] } } diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.get_influencers.json b/x-pack/plugins/console_extensions/spec/generated/ml.get_influencers.json similarity index 80% rename from x-pack/plugins/console_extensions/spec/generated/xpack.ml.get_influencers.json rename to x-pack/plugins/console_extensions/spec/generated/ml.get_influencers.json index a4a0a62a8fb5a..9471fac64d489 100644 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.get_influencers.json +++ b/x-pack/plugins/console_extensions/spec/generated/ml.get_influencers.json @@ -1,5 +1,5 @@ { - "xpack.ml.get_influencers": { + "ml.get_influencers": { "url_params": { "exclude_interim": "__flag__", "from": 0, @@ -15,7 +15,7 @@ "POST" ], "patterns": [ - "_xpack/ml/anomaly_detectors/{job_id}/results/influencers" + "_ml/anomaly_detectors/{job_id}/results/influencers" ], "documentation": "http://www.elastic.co/guide/en/elasticsearch/reference/current/ml-get-influencer.html" } diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.get_job_stats.json b/x-pack/plugins/console_extensions/spec/generated/ml.get_job_stats.json similarity index 65% rename from x-pack/plugins/console_extensions/spec/generated/xpack.ml.get_job_stats.json rename to x-pack/plugins/console_extensions/spec/generated/ml.get_job_stats.json index 644c1a81eb7c5..b28a2655cbefe 100644 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.get_job_stats.json +++ b/x-pack/plugins/console_extensions/spec/generated/ml.get_job_stats.json @@ -1,5 +1,5 @@ { - "xpack.ml.get_job_stats": { + "ml.get_job_stats": { "url_params": { "allow_no_jobs": "__flag__" }, @@ -7,8 +7,8 @@ "GET" ], "patterns": [ - "_xpack/ml/anomaly_detectors/_stats", - "_xpack/ml/anomaly_detectors/{job_id}/_stats" + "_ml/anomaly_detectors/_stats", + "_ml/anomaly_detectors/{job_id}/_stats" ], "documentation": "http://www.elastic.co/guide/en/elasticsearch/reference/current/ml-get-job-stats.html" } diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.get_jobs.json b/x-pack/plugins/console_extensions/spec/generated/ml.get_jobs.json similarity index 68% rename from x-pack/plugins/console_extensions/spec/generated/xpack.ml.get_jobs.json rename to x-pack/plugins/console_extensions/spec/generated/ml.get_jobs.json index 2621d8b56c2ec..8f7de906578d7 100644 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.get_jobs.json +++ b/x-pack/plugins/console_extensions/spec/generated/ml.get_jobs.json @@ -1,5 +1,5 @@ { - "xpack.ml.get_jobs": { + "ml.get_jobs": { "url_params": { "allow_no_jobs": "__flag__" }, @@ -7,8 +7,8 @@ "GET" ], "patterns": [ - "_xpack/ml/anomaly_detectors/{job_id}", - "_xpack/ml/anomaly_detectors" + "_ml/anomaly_detectors/{job_id}", + "_ml/anomaly_detectors" ], "documentation": "http://www.elastic.co/guide/en/elasticsearch/reference/current/ml-get-job.html" } diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.get_model_snapshots.json b/x-pack/plugins/console_extensions/spec/generated/ml.get_model_snapshots.json similarity index 65% rename from x-pack/plugins/console_extensions/spec/generated/xpack.ml.get_model_snapshots.json rename to x-pack/plugins/console_extensions/spec/generated/ml.get_model_snapshots.json index cf82bd258375f..a3b9702f4e4f0 100644 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.get_model_snapshots.json +++ b/x-pack/plugins/console_extensions/spec/generated/ml.get_model_snapshots.json @@ -1,5 +1,5 @@ { - "xpack.ml.get_model_snapshots": { + "ml.get_model_snapshots": { "url_params": { "from": 0, "size": 0, @@ -13,8 +13,8 @@ "POST" ], "patterns": [ - "_xpack/ml/anomaly_detectors/{job_id}/model_snapshots/{snapshot_id}", - "_xpack/ml/anomaly_detectors/{job_id}/model_snapshots" + "_ml/anomaly_detectors/{job_id}/model_snapshots/{snapshot_id}", + "_ml/anomaly_detectors/{job_id}/model_snapshots" ], "documentation": "http://www.elastic.co/guide/en/elasticsearch/reference/current/ml-get-snapshot.html" } diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.get_overall_buckets.json b/x-pack/plugins/console_extensions/spec/generated/ml.get_overall_buckets.json similarity index 79% rename from x-pack/plugins/console_extensions/spec/generated/xpack.ml.get_overall_buckets.json rename to x-pack/plugins/console_extensions/spec/generated/ml.get_overall_buckets.json index ec6da8fb7a2a9..e89d63ae7f49f 100644 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.get_overall_buckets.json +++ b/x-pack/plugins/console_extensions/spec/generated/ml.get_overall_buckets.json @@ -1,5 +1,5 @@ { - "xpack.ml.get_overall_buckets": { + "ml.get_overall_buckets": { "url_params": { "top_n": 0, "bucket_span": "", @@ -14,7 +14,7 @@ "POST" ], "patterns": [ - "_xpack/ml/anomaly_detectors/{job_id}/results/overall_buckets" + "_ml/anomaly_detectors/{job_id}/results/overall_buckets" ], "documentation": "http://www.elastic.co/guide/en/elasticsearch/reference/current/ml-get-overall-buckets.html" } diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.get_records.json b/x-pack/plugins/console_extensions/spec/generated/ml.get_records.json similarity index 81% rename from x-pack/plugins/console_extensions/spec/generated/xpack.ml.get_records.json rename to x-pack/plugins/console_extensions/spec/generated/ml.get_records.json index e8159e97e1829..fd03c8d34214c 100644 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.get_records.json +++ b/x-pack/plugins/console_extensions/spec/generated/ml.get_records.json @@ -1,5 +1,5 @@ { - "xpack.ml.get_records": { + "ml.get_records": { "url_params": { "exclude_interim": "__flag__", "from": 0, @@ -15,7 +15,7 @@ "POST" ], "patterns": [ - "_xpack/ml/anomaly_detectors/{job_id}/results/records" + "_ml/anomaly_detectors/{job_id}/results/records" ], "documentation": "http://www.elastic.co/guide/en/elasticsearch/reference/current/ml-get-record.html" } diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.info.json b/x-pack/plugins/console_extensions/spec/generated/ml.info.json similarity index 60% rename from x-pack/plugins/console_extensions/spec/generated/xpack.ml.info.json rename to x-pack/plugins/console_extensions/spec/generated/ml.info.json index 6235f74cc6227..51b571776ead9 100644 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.info.json +++ b/x-pack/plugins/console_extensions/spec/generated/ml.info.json @@ -1,10 +1,10 @@ { - "xpack.ml.info": { + "ml.info": { "methods": [ "GET" ], "patterns": [ - "_xpack/ml/info" + "_ml/info" ] } } diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.open_job.json b/x-pack/plugins/console_extensions/spec/generated/ml.open_job.json similarity index 69% rename from x-pack/plugins/console_extensions/spec/generated/xpack.ml.open_job.json rename to x-pack/plugins/console_extensions/spec/generated/ml.open_job.json index d45214d813005..cd330ec4822c0 100644 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.open_job.json +++ b/x-pack/plugins/console_extensions/spec/generated/ml.open_job.json @@ -1,10 +1,10 @@ { - "xpack.ml.open_job": { + "ml.open_job": { "methods": [ "POST" ], "patterns": [ - "_xpack/ml/anomaly_detectors/{job_id}/_open" + "_ml/anomaly_detectors/{job_id}/_open" ], "documentation": "http://www.elastic.co/guide/en/elasticsearch/reference/current/ml-open-job.html" } diff --git a/x-pack/plugins/console_extensions/spec/generated/ml.post_calendar_events.json b/x-pack/plugins/console_extensions/spec/generated/ml.post_calendar_events.json new file mode 100644 index 0000000000000..89ce2df63315b --- /dev/null +++ b/x-pack/plugins/console_extensions/spec/generated/ml.post_calendar_events.json @@ -0,0 +1,10 @@ +{ + "ml.post_calendar_events": { + "methods": [ + "POST" + ], + "patterns": [ + "_ml/calendars/{calendar_id}/events" + ] + } +} diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.post_data.json b/x-pack/plugins/console_extensions/spec/generated/ml.post_data.json similarity index 76% rename from x-pack/plugins/console_extensions/spec/generated/xpack.ml.post_data.json rename to x-pack/plugins/console_extensions/spec/generated/ml.post_data.json index 206d2e3e1c064..cc6f0b658e111 100644 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.post_data.json +++ b/x-pack/plugins/console_extensions/spec/generated/ml.post_data.json @@ -1,5 +1,5 @@ { - "xpack.ml.post_data": { + "ml.post_data": { "url_params": { "reset_start": "", "reset_end": "" @@ -8,7 +8,7 @@ "POST" ], "patterns": [ - "_xpack/ml/anomaly_detectors/{job_id}/_data" + "_ml/anomaly_detectors/{job_id}/_data" ], "documentation": "http://www.elastic.co/guide/en/elasticsearch/reference/current/ml-post-data.html" } diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.preview_datafeed.json b/x-pack/plugins/console_extensions/spec/generated/ml.preview_datafeed.json similarity index 68% rename from x-pack/plugins/console_extensions/spec/generated/xpack.ml.preview_datafeed.json rename to x-pack/plugins/console_extensions/spec/generated/ml.preview_datafeed.json index 9aeca7e1363a0..be3c3d466f37d 100644 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.preview_datafeed.json +++ b/x-pack/plugins/console_extensions/spec/generated/ml.preview_datafeed.json @@ -1,10 +1,10 @@ { - "xpack.ml.preview_datafeed": { + "ml.preview_datafeed": { "methods": [ "GET" ], "patterns": [ - "_xpack/ml/datafeeds/{datafeed_id}/_preview" + "_ml/datafeeds/{datafeed_id}/_preview" ], "documentation": "http://www.elastic.co/guide/en/elasticsearch/reference/current/ml-preview-datafeed.html" } diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.put_filter.json b/x-pack/plugins/console_extensions/spec/generated/ml.put_calendar.json similarity index 51% rename from x-pack/plugins/console_extensions/spec/generated/xpack.ml.put_filter.json rename to x-pack/plugins/console_extensions/spec/generated/ml.put_calendar.json index 2a2c5815046d4..7452d1b4b9707 100644 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.put_filter.json +++ b/x-pack/plugins/console_extensions/spec/generated/ml.put_calendar.json @@ -1,10 +1,10 @@ { - "xpack.ml.put_filter": { + "ml.put_calendar": { "methods": [ "PUT" ], "patterns": [ - "_xpack/ml/filters/{filter_id}" + "_ml/calendars/{calendar_id}" ] } } diff --git a/x-pack/plugins/console_extensions/spec/generated/ml.put_calendar_job.json b/x-pack/plugins/console_extensions/spec/generated/ml.put_calendar_job.json new file mode 100644 index 0000000000000..08dc3d77b5f25 --- /dev/null +++ b/x-pack/plugins/console_extensions/spec/generated/ml.put_calendar_job.json @@ -0,0 +1,10 @@ +{ + "ml.put_calendar_job": { + "methods": [ + "PUT" + ], + "patterns": [ + "_ml/calendars/{calendar_id}/jobs/{job_id}" + ] + } +} diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.put_datafeed.json b/x-pack/plugins/console_extensions/spec/generated/ml.put_datafeed.json similarity index 71% rename from x-pack/plugins/console_extensions/spec/generated/xpack.ml.put_datafeed.json rename to x-pack/plugins/console_extensions/spec/generated/ml.put_datafeed.json index db5cb27f4a790..a61f9ab465724 100644 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.put_datafeed.json +++ b/x-pack/plugins/console_extensions/spec/generated/ml.put_datafeed.json @@ -1,10 +1,10 @@ { - "xpack.ml.put_datafeed": { + "ml.put_datafeed": { "methods": [ "PUT" ], "patterns": [ - "_xpack/ml/datafeeds/{datafeed_id}" + "_ml/datafeeds/{datafeed_id}" ], "documentation": "http://www.elastic.co/guide/en/elasticsearch/reference/current/ml-put-datafeed.html" } diff --git a/x-pack/plugins/console_extensions/spec/generated/ml.put_filter.json b/x-pack/plugins/console_extensions/spec/generated/ml.put_filter.json new file mode 100644 index 0000000000000..6d57c433d71f4 --- /dev/null +++ b/x-pack/plugins/console_extensions/spec/generated/ml.put_filter.json @@ -0,0 +1,10 @@ +{ + "ml.put_filter": { + "methods": [ + "PUT" + ], + "patterns": [ + "_ml/filters/{filter_id}" + ] + } +} diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.put_job.json b/x-pack/plugins/console_extensions/spec/generated/ml.put_job.json similarity index 71% rename from x-pack/plugins/console_extensions/spec/generated/xpack.ml.put_job.json rename to x-pack/plugins/console_extensions/spec/generated/ml.put_job.json index ecd8f59d77574..d8e38a0bd4b9d 100644 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.put_job.json +++ b/x-pack/plugins/console_extensions/spec/generated/ml.put_job.json @@ -1,10 +1,10 @@ { - "xpack.ml.put_job": { + "ml.put_job": { "methods": [ "PUT" ], "patterns": [ - "_xpack/ml/anomaly_detectors/{job_id}" + "_ml/anomaly_detectors/{job_id}" ], "documentation": "http://www.elastic.co/guide/en/elasticsearch/reference/current/ml-put-job.html" } diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.revert_model_snapshot.json b/x-pack/plugins/console_extensions/spec/generated/ml.revert_model_snapshot.json similarity index 67% rename from x-pack/plugins/console_extensions/spec/generated/xpack.ml.revert_model_snapshot.json rename to x-pack/plugins/console_extensions/spec/generated/ml.revert_model_snapshot.json index 9193cbb80d2fd..7b6d74d47a711 100644 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.revert_model_snapshot.json +++ b/x-pack/plugins/console_extensions/spec/generated/ml.revert_model_snapshot.json @@ -1,5 +1,5 @@ { - "xpack.ml.revert_model_snapshot": { + "ml.revert_model_snapshot": { "url_params": { "delete_intervening_results": "__flag__" }, @@ -7,7 +7,7 @@ "POST" ], "patterns": [ - "_xpack/ml/anomaly_detectors/{job_id}/model_snapshots/{snapshot_id}/_revert" + "_ml/anomaly_detectors/{job_id}/model_snapshots/{snapshot_id}/_revert" ], "documentation": "http://www.elastic.co/guide/en/elasticsearch/reference/current/ml-revert-snapshot.html" } diff --git a/x-pack/plugins/console_extensions/spec/generated/ml.set_upgrade_mode.json b/x-pack/plugins/console_extensions/spec/generated/ml.set_upgrade_mode.json new file mode 100644 index 0000000000000..ad1734033d9a5 --- /dev/null +++ b/x-pack/plugins/console_extensions/spec/generated/ml.set_upgrade_mode.json @@ -0,0 +1,15 @@ +{ + "ml.set_upgrade_mode": { + "url_params": { + "enabled": "__flag__", + "timeout": "" + }, + "methods": [ + "POST" + ], + "patterns": [ + "_ml/set_upgrade_mode" + ], + "documentation": "TODO" + } +} diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.start_datafeed.json b/x-pack/plugins/console_extensions/spec/generated/ml.start_datafeed.json similarity index 76% rename from x-pack/plugins/console_extensions/spec/generated/xpack.ml.start_datafeed.json rename to x-pack/plugins/console_extensions/spec/generated/ml.start_datafeed.json index 40479c8c3f687..8171a792d7e33 100644 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.start_datafeed.json +++ b/x-pack/plugins/console_extensions/spec/generated/ml.start_datafeed.json @@ -1,5 +1,5 @@ { - "xpack.ml.start_datafeed": { + "ml.start_datafeed": { "url_params": { "start": "", "end": "", @@ -9,7 +9,7 @@ "POST" ], "patterns": [ - "_xpack/ml/datafeeds/{datafeed_id}/_start" + "_ml/datafeeds/{datafeed_id}/_start" ], "documentation": "http://www.elastic.co/guide/en/elasticsearch/reference/current/ml-start-datafeed.html" } diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.stop_datafeed.json b/x-pack/plugins/console_extensions/spec/generated/ml.stop_datafeed.json similarity index 78% rename from x-pack/plugins/console_extensions/spec/generated/xpack.ml.stop_datafeed.json rename to x-pack/plugins/console_extensions/spec/generated/ml.stop_datafeed.json index 2af514532f16a..b10fed7010a7f 100644 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.stop_datafeed.json +++ b/x-pack/plugins/console_extensions/spec/generated/ml.stop_datafeed.json @@ -1,5 +1,5 @@ { - "xpack.ml.stop_datafeed": { + "ml.stop_datafeed": { "url_params": { "allow_no_datafeeds": "__flag__", "force": "__flag__", @@ -9,7 +9,7 @@ "POST" ], "patterns": [ - "_xpack/ml/datafeeds/{datafeed_id}/_stop" + "_ml/datafeeds/{datafeed_id}/_stop" ], "documentation": "http://www.elastic.co/guide/en/elasticsearch/reference/current/ml-stop-datafeed.html" } diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.update_datafeed.json b/x-pack/plugins/console_extensions/spec/generated/ml.update_datafeed.json similarity index 68% rename from x-pack/plugins/console_extensions/spec/generated/xpack.ml.update_datafeed.json rename to x-pack/plugins/console_extensions/spec/generated/ml.update_datafeed.json index 953e08dd08c17..9c0d7502d2fbe 100644 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.update_datafeed.json +++ b/x-pack/plugins/console_extensions/spec/generated/ml.update_datafeed.json @@ -1,10 +1,10 @@ { - "xpack.ml.update_datafeed": { + "ml.update_datafeed": { "methods": [ "POST" ], "patterns": [ - "_xpack/ml/datafeeds/{datafeed_id}/_update" + "_ml/datafeeds/{datafeed_id}/_update" ], "documentation": "http://www.elastic.co/guide/en/elasticsearch/reference/current/ml-update-datafeed.html" } diff --git a/x-pack/plugins/console_extensions/spec/generated/ml.update_filter.json b/x-pack/plugins/console_extensions/spec/generated/ml.update_filter.json new file mode 100644 index 0000000000000..3f48013c2be1c --- /dev/null +++ b/x-pack/plugins/console_extensions/spec/generated/ml.update_filter.json @@ -0,0 +1,10 @@ +{ + "ml.update_filter": { + "methods": [ + "POST" + ], + "patterns": [ + "_ml/filters/{filter_id}/_update" + ] + } +} diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.update_job.json b/x-pack/plugins/console_extensions/spec/generated/ml.update_job.json similarity index 68% rename from x-pack/plugins/console_extensions/spec/generated/xpack.ml.update_job.json rename to x-pack/plugins/console_extensions/spec/generated/ml.update_job.json index e3c8abf72d908..7276183b2e0c9 100644 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.update_job.json +++ b/x-pack/plugins/console_extensions/spec/generated/ml.update_job.json @@ -1,10 +1,10 @@ { - "xpack.ml.update_job": { + "ml.update_job": { "methods": [ "POST" ], "patterns": [ - "_xpack/ml/anomaly_detectors/{job_id}/_update" + "_ml/anomaly_detectors/{job_id}/_update" ], "documentation": "http://www.elastic.co/guide/en/elasticsearch/reference/current/ml-update-job.html" } diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.update_model_snapshot.json b/x-pack/plugins/console_extensions/spec/generated/ml.update_model_snapshot.json similarity index 59% rename from x-pack/plugins/console_extensions/spec/generated/xpack.ml.update_model_snapshot.json rename to x-pack/plugins/console_extensions/spec/generated/ml.update_model_snapshot.json index 848950d6f5394..80e533eb55826 100644 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.update_model_snapshot.json +++ b/x-pack/plugins/console_extensions/spec/generated/ml.update_model_snapshot.json @@ -1,10 +1,10 @@ { - "xpack.ml.update_model_snapshot": { + "ml.update_model_snapshot": { "methods": [ "POST" ], "patterns": [ - "_xpack/ml/anomaly_detectors/{job_id}/model_snapshots/{snapshot_id}/_update" + "_ml/anomaly_detectors/{job_id}/model_snapshots/{snapshot_id}/_update" ], "documentation": "http://www.elastic.co/guide/en/elasticsearch/reference/current/ml-update-snapshot.html" } diff --git a/x-pack/plugins/console_extensions/spec/generated/ml.upgrade.json b/x-pack/plugins/console_extensions/spec/generated/ml.upgrade.json new file mode 100644 index 0000000000000..4603b7d5a5e4d --- /dev/null +++ b/x-pack/plugins/console_extensions/spec/generated/ml.upgrade.json @@ -0,0 +1,14 @@ +{ + "ml.upgrade": { + "url_params": { + "wait_for_completion": "__flag__" + }, + "methods": [ + "POST" + ], + "patterns": [ + "_ml/_upgrade" + ], + "documentation": "TODO" + } +} diff --git a/x-pack/plugins/console_extensions/spec/generated/ml.validate.json b/x-pack/plugins/console_extensions/spec/generated/ml.validate.json new file mode 100644 index 0000000000000..298c7cf6dce26 --- /dev/null +++ b/x-pack/plugins/console_extensions/spec/generated/ml.validate.json @@ -0,0 +1,10 @@ +{ + "ml.validate": { + "methods": [ + "POST" + ], + "patterns": [ + "_ml/anomaly_detectors/_validate" + ] + } +} diff --git a/x-pack/plugins/console_extensions/spec/generated/ml.validate_detector.json b/x-pack/plugins/console_extensions/spec/generated/ml.validate_detector.json new file mode 100644 index 0000000000000..5e6d7daeea772 --- /dev/null +++ b/x-pack/plugins/console_extensions/spec/generated/ml.validate_detector.json @@ -0,0 +1,10 @@ +{ + "ml.validate_detector": { + "methods": [ + "POST" + ], + "patterns": [ + "_ml/anomaly_detectors/_validate/detector" + ] + } +} diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.monitoring.bulk.json b/x-pack/plugins/console_extensions/spec/generated/monitoring.bulk.json similarity index 73% rename from x-pack/plugins/console_extensions/spec/generated/xpack.monitoring.bulk.json rename to x-pack/plugins/console_extensions/spec/generated/monitoring.bulk.json index f98aa70ce7cd3..7cb56a9d79d6e 100644 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.monitoring.bulk.json +++ b/x-pack/plugins/console_extensions/spec/generated/monitoring.bulk.json @@ -1,5 +1,5 @@ { - "xpack.monitoring.bulk": { + "monitoring.bulk": { "url_params": { "system_id": "", "system_api_version": "", @@ -10,8 +10,8 @@ "PUT" ], "patterns": [ - "_xpack/monitoring/_bulk", - "_xpack/monitoring/{type}/_bulk" + "_monitoring/bulk", + "_monitoring/{type}/bulk" ], "documentation": "http://www.elastic.co/guide/en/monitoring/current/appendix-api-bulk.html" } diff --git a/x-pack/plugins/console_extensions/spec/generated/security.get_user_privileges.json b/x-pack/plugins/console_extensions/spec/generated/security.get_user_privileges.json index e5ada00828f73..1b939d2d9a29e 100644 --- a/x-pack/plugins/console_extensions/spec/generated/security.get_user_privileges.json +++ b/x-pack/plugins/console_extensions/spec/generated/security.get_user_privileges.json @@ -5,6 +5,7 @@ ], "patterns": [ "_security/user/_privileges" - ] + ], + "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-get-user-privileges.html" } } diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.graph.explore.json b/x-pack/plugins/console_extensions/spec/generated/xpack.graph.explore.json index f140af08686e2..b2a74408995d7 100644 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.graph.explore.json +++ b/x-pack/plugins/console_extensions/spec/generated/xpack.graph.explore.json @@ -9,8 +9,8 @@ "POST" ], "patterns": [ - "{indices}/_xpack/graph/_explore", - "{indices}/{type}/_xpack/graph/_explore" + "{indices}/_graph/explore", + "{indices}/{type}/_graph/explore" ], "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/current/graph-explore-api.html" } diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.license.delete.json b/x-pack/plugins/console_extensions/spec/generated/xpack.license.delete.json index a60c3b8f70f5c..492a0403be62c 100644 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.license.delete.json +++ b/x-pack/plugins/console_extensions/spec/generated/xpack.license.delete.json @@ -4,7 +4,7 @@ "DELETE" ], "patterns": [ - "_xpack/license" + "_license" ], "documentation": "https://www.elastic.co/guide/en/x-pack/current/license-management.html" } diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.license.get.json b/x-pack/plugins/console_extensions/spec/generated/xpack.license.get.json index f9ca4bc285ecb..ccd7d7eb77914 100644 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.license.get.json +++ b/x-pack/plugins/console_extensions/spec/generated/xpack.license.get.json @@ -7,7 +7,7 @@ "GET" ], "patterns": [ - "_xpack/license" + "_license" ], "documentation": "https://www.elastic.co/guide/en/x-pack/current/license-management.html" } diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.license.get_basic_status.json b/x-pack/plugins/console_extensions/spec/generated/xpack.license.get_basic_status.json index c83cdd4a95a8d..5a5488208e218 100644 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.license.get_basic_status.json +++ b/x-pack/plugins/console_extensions/spec/generated/xpack.license.get_basic_status.json @@ -4,7 +4,7 @@ "GET" ], "patterns": [ - "_xpack/license/basic_status" + "_license/basic_status" ], "documentation": "https://www.elastic.co/guide/en/x-pack/current/license-management.html" } diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.license.get_trial_status.json b/x-pack/plugins/console_extensions/spec/generated/xpack.license.get_trial_status.json index cd79e6fdc2c1e..251d899febf53 100644 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.license.get_trial_status.json +++ b/x-pack/plugins/console_extensions/spec/generated/xpack.license.get_trial_status.json @@ -4,7 +4,7 @@ "GET" ], "patterns": [ - "_xpack/license/trial_status" + "_license/trial_status" ], "documentation": "https://www.elastic.co/guide/en/x-pack/current/license-management.html" } diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.license.post.json b/x-pack/plugins/console_extensions/spec/generated/xpack.license.post.json index 6c5e2c65a56d6..79be105710904 100644 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.license.post.json +++ b/x-pack/plugins/console_extensions/spec/generated/xpack.license.post.json @@ -8,7 +8,7 @@ "POST" ], "patterns": [ - "_xpack/license" + "_license" ], "documentation": "https://www.elastic.co/guide/en/x-pack/current/license-management.html" } diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.license.post_start_basic.json b/x-pack/plugins/console_extensions/spec/generated/xpack.license.post_start_basic.json index 27da929b25eab..fb858a65c2322 100644 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.license.post_start_basic.json +++ b/x-pack/plugins/console_extensions/spec/generated/xpack.license.post_start_basic.json @@ -7,7 +7,7 @@ "POST" ], "patterns": [ - "_xpack/license/start_basic" + "_license/start_basic" ], "documentation": "https://www.elastic.co/guide/en/x-pack/current/license-management.html" } diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.license.post_start_trial.json b/x-pack/plugins/console_extensions/spec/generated/xpack.license.post_start_trial.json index 905b8b324d5ef..bd3eaf476e022 100644 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.license.post_start_trial.json +++ b/x-pack/plugins/console_extensions/spec/generated/xpack.license.post_start_trial.json @@ -8,7 +8,7 @@ "POST" ], "patterns": [ - "_xpack/license/start_trial" + "_license/start_trial" ], "documentation": "https://www.elastic.co/guide/en/x-pack/current/license-management.html" } diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.migration.deprecations.json b/x-pack/plugins/console_extensions/spec/generated/xpack.migration.deprecations.json index d8dbea2c53d7a..dad2695f0d14c 100644 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.migration.deprecations.json +++ b/x-pack/plugins/console_extensions/spec/generated/xpack.migration.deprecations.json @@ -4,8 +4,8 @@ "GET" ], "patterns": [ - "_xpack/migration/deprecations", - "{indices}/_xpack/migration/deprecations" + "_migration/deprecations", + "{indices}/_migration/deprecations" ], "documentation": "http://www.elastic.co/guide/en/migration/current/migration-api-deprecation.html" } diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.migration.get_assistance.json b/x-pack/plugins/console_extensions/spec/generated/xpack.migration.get_assistance.json index dcd81df3770a2..4cc068ce2e460 100644 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.migration.get_assistance.json +++ b/x-pack/plugins/console_extensions/spec/generated/xpack.migration.get_assistance.json @@ -14,8 +14,8 @@ "GET" ], "patterns": [ - "_xpack/migration/assistance", - "_xpack/migration/assistance/{indices}" + "_migration/assistance", + "_migration/assistance/{indices}" ], "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/current/migration-api-assistance.html" } diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.migration.upgrade.json b/x-pack/plugins/console_extensions/spec/generated/xpack.migration.upgrade.json index 331a93f176320..93186f31a0876 100644 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.migration.upgrade.json +++ b/x-pack/plugins/console_extensions/spec/generated/xpack.migration.upgrade.json @@ -7,7 +7,7 @@ "POST" ], "patterns": [ - "_xpack/migration/upgrade/{indices}" + "_migration/upgrade/{indices}" ], "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/current/migration-api-upgrade.html" } diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.delete_calendar.json b/x-pack/plugins/console_extensions/spec/generated/xpack.ml.delete_calendar.json deleted file mode 100644 index d5e47c1d78c7c..0000000000000 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.delete_calendar.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "xpack.ml.delete_calendar": { - "methods": [ - "DELETE" - ], - "patterns": [ - "_xpack/ml/calendars/{calendar_id}" - ] - } -} diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.delete_calendar_event.json b/x-pack/plugins/console_extensions/spec/generated/xpack.ml.delete_calendar_event.json deleted file mode 100644 index 2be7a261b1d20..0000000000000 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.delete_calendar_event.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "xpack.ml.delete_calendar_event": { - "methods": [ - "DELETE" - ], - "patterns": [ - "_xpack/ml/calendars/{calendar_id}/events/{event_id}" - ] - } -} diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.delete_calendar_job.json b/x-pack/plugins/console_extensions/spec/generated/xpack.ml.delete_calendar_job.json deleted file mode 100644 index 7d3716f9ebfea..0000000000000 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.delete_calendar_job.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "xpack.ml.delete_calendar_job": { - "methods": [ - "DELETE" - ], - "patterns": [ - "_xpack/ml/calendars/{calendar_id}/jobs/{job_id}" - ] - } -} diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.delete_expired_data.json b/x-pack/plugins/console_extensions/spec/generated/xpack.ml.delete_expired_data.json deleted file mode 100644 index e8c04ff315731..0000000000000 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.delete_expired_data.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "xpack.ml.delete_expired_data": { - "methods": [ - "DELETE" - ], - "patterns": [ - "_xpack/ml/_delete_expired_data" - ] - } -} diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.post_calendar_events.json b/x-pack/plugins/console_extensions/spec/generated/xpack.ml.post_calendar_events.json deleted file mode 100644 index ec580842f5120..0000000000000 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.post_calendar_events.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "xpack.ml.post_calendar_events": { - "methods": [ - "POST" - ], - "patterns": [ - "_xpack/ml/calendars/{calendar_id}/events" - ] - } -} diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.put_calendar.json b/x-pack/plugins/console_extensions/spec/generated/xpack.ml.put_calendar.json deleted file mode 100644 index 706373c53e852..0000000000000 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.put_calendar.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "xpack.ml.put_calendar": { - "methods": [ - "PUT" - ], - "patterns": [ - "_xpack/ml/calendars/{calendar_id}" - ] - } -} diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.put_calendar_job.json b/x-pack/plugins/console_extensions/spec/generated/xpack.ml.put_calendar_job.json deleted file mode 100644 index 98a4db0bf0e0e..0000000000000 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.put_calendar_job.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "xpack.ml.put_calendar_job": { - "methods": [ - "PUT" - ], - "patterns": [ - "_xpack/ml/calendars/{calendar_id}/jobs/{job_id}" - ] - } -} diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.validate.json b/x-pack/plugins/console_extensions/spec/generated/xpack.ml.validate.json deleted file mode 100644 index 9af94e51db0f5..0000000000000 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.validate.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "xpack.ml.validate": { - "methods": [ - "POST" - ], - "patterns": [ - "_xpack/ml/anomaly_detectors/_validate" - ] - } -} diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.validate_detector.json b/x-pack/plugins/console_extensions/spec/generated/xpack.ml.validate_detector.json deleted file mode 100644 index de0d19b09619a..0000000000000 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.ml.validate_detector.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "xpack.ml.validate_detector": { - "methods": [ - "POST" - ], - "patterns": [ - "_xpack/ml/anomaly_detectors/_validate/detector" - ] - } -} diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.rollup.delete_job.json b/x-pack/plugins/console_extensions/spec/generated/xpack.rollup.delete_job.json index a7c381c2ee7f9..586ebd6ff6723 100644 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.rollup.delete_job.json +++ b/x-pack/plugins/console_extensions/spec/generated/xpack.rollup.delete_job.json @@ -4,7 +4,7 @@ "DELETE" ], "patterns": [ - "_xpack/rollup/job/{id}" + "_rollup/job/{id}" ] } } diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.rollup.get_jobs.json b/x-pack/plugins/console_extensions/spec/generated/xpack.rollup.get_jobs.json index 54c535f3a81f7..657b68fc214b0 100644 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.rollup.get_jobs.json +++ b/x-pack/plugins/console_extensions/spec/generated/xpack.rollup.get_jobs.json @@ -4,8 +4,8 @@ "GET" ], "patterns": [ - "_xpack/rollup/job/{id}", - "_xpack/rollup/job/" + "_rollup/job/{id}", + "_rollup/job/" ] } } diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.rollup.get_rollup_caps.json b/x-pack/plugins/console_extensions/spec/generated/xpack.rollup.get_rollup_caps.json index f619949e63be1..a8145f69eb50b 100644 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.rollup.get_rollup_caps.json +++ b/x-pack/plugins/console_extensions/spec/generated/xpack.rollup.get_rollup_caps.json @@ -4,8 +4,8 @@ "GET" ], "patterns": [ - "_xpack/rollup/data/{id}", - "_xpack/rollup/data/" + "_rollup/data/{id}", + "_rollup/data/" ] } } diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.rollup.get_rollup_index_caps.json b/x-pack/plugins/console_extensions/spec/generated/xpack.rollup.get_rollup_index_caps.json new file mode 100644 index 0000000000000..e2f94ae5648ec --- /dev/null +++ b/x-pack/plugins/console_extensions/spec/generated/xpack.rollup.get_rollup_index_caps.json @@ -0,0 +1,10 @@ +{ + "xpack.rollup.get_rollup_index_caps": { + "methods": [ + "GET" + ], + "patterns": [ + "{indices}/_rollup/data" + ] + } +} diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.rollup.put_job.json b/x-pack/plugins/console_extensions/spec/generated/xpack.rollup.put_job.json index d55fe948c450c..e36ec37c93b68 100644 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.rollup.put_job.json +++ b/x-pack/plugins/console_extensions/spec/generated/xpack.rollup.put_job.json @@ -4,7 +4,7 @@ "PUT" ], "patterns": [ - "_xpack/rollup/job/{id}" + "_rollup/job/{id}" ] } } diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.rollup.rollup_search.json b/x-pack/plugins/console_extensions/spec/generated/xpack.rollup.rollup_search.json index 544d86249b40d..f6a8caff17201 100644 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.rollup.rollup_search.json +++ b/x-pack/plugins/console_extensions/spec/generated/xpack.rollup.rollup_search.json @@ -1,5 +1,9 @@ { "xpack.rollup.rollup_search": { + "url_params": { + "typed_keys": "__flag__", + "rest_total_hits_as_int": "__flag__" + }, "methods": [ "GET", "POST" diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.rollup.start_job.json b/x-pack/plugins/console_extensions/spec/generated/xpack.rollup.start_job.json index 7a1323f1f79b3..e7b1f48f8cdf5 100644 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.rollup.start_job.json +++ b/x-pack/plugins/console_extensions/spec/generated/xpack.rollup.start_job.json @@ -4,7 +4,7 @@ "POST" ], "patterns": [ - "_xpack/rollup/job/{id}/_start" + "_rollup/job/{id}/_start" ] } } diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.rollup.stop_job.json b/x-pack/plugins/console_extensions/spec/generated/xpack.rollup.stop_job.json index e185077e35f16..58706694e2ff4 100644 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.rollup.stop_job.json +++ b/x-pack/plugins/console_extensions/spec/generated/xpack.rollup.stop_job.json @@ -1,10 +1,14 @@ { "xpack.rollup.stop_job": { + "url_params": { + "wait_for_completion": "__flag__", + "timeout": "" + }, "methods": [ "POST" ], "patterns": [ - "_xpack/rollup/job/{id}/_stop" + "_rollup/job/{id}/_stop" ] } } diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.security.authenticate.json b/x-pack/plugins/console_extensions/spec/generated/xpack.security.authenticate.json deleted file mode 100644 index 6ebef7ce874d9..0000000000000 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.security.authenticate.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "xpack.security.authenticate": { - "methods": [ - "GET" - ], - "patterns": [ - "_xpack/security/_authenticate" - ], - "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-authenticate.html" - } -} diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.security.change_password.json b/x-pack/plugins/console_extensions/spec/generated/xpack.security.change_password.json deleted file mode 100644 index a811501e4d71f..0000000000000 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.security.change_password.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "xpack.security.change_password": { - "url_params": { - "refresh": [ - "true", - "false", - "wait_for" - ] - }, - "methods": [ - "PUT", - "POST" - ], - "patterns": [ - "_xpack/security/user/{username}/_password", - "_xpack/security/user/_password" - ], - "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-change-password.html" - } -} diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.security.clear_cached_realms.json b/x-pack/plugins/console_extensions/spec/generated/xpack.security.clear_cached_realms.json deleted file mode 100644 index 2ef0bfaf79df1..0000000000000 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.security.clear_cached_realms.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "xpack.security.clear_cached_realms": { - "url_params": { - "usernames": [] - }, - "methods": [ - "POST" - ], - "patterns": [ - "_xpack/security/realm/{realms}/_clear_cache" - ], - "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-clear-cache.html" - } -} diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.security.clear_cached_roles.json b/x-pack/plugins/console_extensions/spec/generated/xpack.security.clear_cached_roles.json deleted file mode 100644 index 47baad3ef989f..0000000000000 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.security.clear_cached_roles.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "xpack.security.clear_cached_roles": { - "methods": [ - "POST" - ], - "patterns": [ - "_xpack/security/role/{name}/_clear_cache" - ], - "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-clear-role-cache.html" - } -} diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.security.delete_role.json b/x-pack/plugins/console_extensions/spec/generated/xpack.security.delete_role.json deleted file mode 100644 index ad95ac444ef98..0000000000000 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.security.delete_role.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "xpack.security.delete_role": { - "url_params": { - "refresh": [ - "true", - "false", - "wait_for" - ] - }, - "methods": [ - "DELETE" - ], - "patterns": [ - "_xpack/security/role/{name}" - ], - "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-delete-role.html" - } -} diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.security.delete_role_mapping.json b/x-pack/plugins/console_extensions/spec/generated/xpack.security.delete_role_mapping.json deleted file mode 100644 index 0734c784b80f8..0000000000000 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.security.delete_role_mapping.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "xpack.security.delete_role_mapping": { - "url_params": { - "refresh": [ - "true", - "false", - "wait_for" - ] - }, - "methods": [ - "DELETE" - ], - "patterns": [ - "_xpack/security/role_mapping/{name}" - ], - "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-delete-role-mapping.html" - } -} diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.security.delete_user.json b/x-pack/plugins/console_extensions/spec/generated/xpack.security.delete_user.json deleted file mode 100644 index 732721143503e..0000000000000 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.security.delete_user.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "xpack.security.delete_user": { - "url_params": { - "refresh": [ - "true", - "false", - "wait_for" - ] - }, - "methods": [ - "DELETE" - ], - "patterns": [ - "_xpack/security/user/{username}" - ], - "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-delete-user.html" - } -} diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.security.disable_user.json b/x-pack/plugins/console_extensions/spec/generated/xpack.security.disable_user.json deleted file mode 100644 index b9151cf3685a1..0000000000000 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.security.disable_user.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "xpack.security.disable_user": { - "url_params": { - "refresh": [ - "true", - "false", - "wait_for" - ] - }, - "methods": [ - "PUT", - "POST" - ], - "patterns": [ - "_xpack/security/user/{username}/_disable" - ], - "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-disable-user.html" - } -} diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.security.enable_user.json b/x-pack/plugins/console_extensions/spec/generated/xpack.security.enable_user.json deleted file mode 100644 index 12d1b3c7cf0bb..0000000000000 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.security.enable_user.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "xpack.security.enable_user": { - "url_params": { - "refresh": [ - "true", - "false", - "wait_for" - ] - }, - "methods": [ - "PUT", - "POST" - ], - "patterns": [ - "_xpack/security/user/{username}/_enable" - ], - "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-enable-user.html" - } -} diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.security.get_role.json b/x-pack/plugins/console_extensions/spec/generated/xpack.security.get_role.json deleted file mode 100644 index e4b887e3d9329..0000000000000 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.security.get_role.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "xpack.security.get_role": { - "methods": [ - "GET" - ], - "patterns": [ - "_xpack/security/role/{name}", - "_xpack/security/role" - ], - "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-get-role.html" - } -} diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.security.get_role_mapping.json b/x-pack/plugins/console_extensions/spec/generated/xpack.security.get_role_mapping.json deleted file mode 100644 index 45c935277c865..0000000000000 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.security.get_role_mapping.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "xpack.security.get_role_mapping": { - "methods": [ - "GET" - ], - "patterns": [ - "_xpack/security/role_mapping/{name}", - "_xpack/security/role_mapping" - ], - "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-get-role-mapping.html" - } -} diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.security.get_token.json b/x-pack/plugins/console_extensions/spec/generated/xpack.security.get_token.json deleted file mode 100644 index 208d6e31346b7..0000000000000 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.security.get_token.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "xpack.security.get_token": { - "methods": [ - "POST" - ], - "patterns": [ - "_xpack/security/oauth2/token" - ], - "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-get-token.html" - } -} diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.security.get_user.json b/x-pack/plugins/console_extensions/spec/generated/xpack.security.get_user.json deleted file mode 100644 index d42d1afbb91c9..0000000000000 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.security.get_user.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "xpack.security.get_user": { - "methods": [ - "GET" - ], - "patterns": [ - "_xpack/security/user/{username}", - "_xpack/security/user" - ], - "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-get-user.html" - } -} diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.security.invalidate_token.json b/x-pack/plugins/console_extensions/spec/generated/xpack.security.invalidate_token.json deleted file mode 100644 index e9ffac7bd5f92..0000000000000 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.security.invalidate_token.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "xpack.security.invalidate_token": { - "methods": [ - "DELETE" - ], - "patterns": [ - "_xpack/security/oauth2/token" - ], - "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-invalidate-token.html" - } -} diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.security.put_role.json b/x-pack/plugins/console_extensions/spec/generated/xpack.security.put_role.json deleted file mode 100644 index 6cc3523f8b647..0000000000000 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.security.put_role.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "xpack.security.put_role": { - "url_params": { - "refresh": [ - "true", - "false", - "wait_for" - ] - }, - "methods": [ - "PUT", - "POST" - ], - "patterns": [ - "_xpack/security/role/{name}" - ], - "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-put-role.html" - } -} diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.security.put_role_mapping.json b/x-pack/plugins/console_extensions/spec/generated/xpack.security.put_role_mapping.json deleted file mode 100644 index 7a426c420fffd..0000000000000 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.security.put_role_mapping.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "xpack.security.put_role_mapping": { - "url_params": { - "refresh": [ - "true", - "false", - "wait_for" - ] - }, - "methods": [ - "PUT", - "POST" - ], - "patterns": [ - "_xpack/security/role_mapping/{name}" - ], - "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-put-role-mapping.html" - } -} diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.security.put_user.json b/x-pack/plugins/console_extensions/spec/generated/xpack.security.put_user.json deleted file mode 100644 index 19a13b37c7d91..0000000000000 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.security.put_user.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "xpack.security.put_user": { - "url_params": { - "refresh": [ - "true", - "false", - "wait_for" - ] - }, - "methods": [ - "PUT", - "POST" - ], - "patterns": [ - "_xpack/security/user/{username}" - ], - "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-put-user.html" - } -} diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.sql.clear_cursor.json b/x-pack/plugins/console_extensions/spec/generated/xpack.sql.clear_cursor.json index f1b357e909bd6..037df31e3277a 100644 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.sql.clear_cursor.json +++ b/x-pack/plugins/console_extensions/spec/generated/xpack.sql.clear_cursor.json @@ -4,7 +4,7 @@ "POST" ], "patterns": [ - "_xpack/sql/close" + "_sql/close" ], "documentation": "Clear SQL cursor" } diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.sql.query.json b/x-pack/plugins/console_extensions/spec/generated/xpack.sql.query.json index 20b8502f79bfc..6f1f9fda541a6 100644 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.sql.query.json +++ b/x-pack/plugins/console_extensions/spec/generated/xpack.sql.query.json @@ -8,7 +8,7 @@ "GET" ], "patterns": [ - "_xpack/sql" + "_sql" ], "documentation": "Execute SQL" } diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.sql.translate.json b/x-pack/plugins/console_extensions/spec/generated/xpack.sql.translate.json index 737a57f9f5ca6..32f49659194a1 100644 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.sql.translate.json +++ b/x-pack/plugins/console_extensions/spec/generated/xpack.sql.translate.json @@ -5,7 +5,7 @@ "GET" ], "patterns": [ - "_xpack/sql/translate" + "_sql/translate" ], "documentation": "Translate SQL into Elasticsearch queries" } diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.watcher.ack_watch.json b/x-pack/plugins/console_extensions/spec/generated/xpack.watcher.ack_watch.json index 53edb6183e2d9..acda1c9e716dd 100644 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.watcher.ack_watch.json +++ b/x-pack/plugins/console_extensions/spec/generated/xpack.watcher.ack_watch.json @@ -5,8 +5,8 @@ "POST" ], "patterns": [ - "_xpack/watcher/watch/{watch_id}/_ack", - "_xpack/watcher/watch/{watch_id}/_ack/{action_id}" + "_watcher/watch/{watch_id}/_ack", + "_watcher/watch/{watch_id}/_ack/{action_id}" ], "documentation": "http://www.elastic.co/guide/en/elasticsearch/reference/current/watcher-api-ack-watch.html" } diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.watcher.activate_watch.json b/x-pack/plugins/console_extensions/spec/generated/xpack.watcher.activate_watch.json index 57a1804b8918d..d69d581f01195 100644 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.watcher.activate_watch.json +++ b/x-pack/plugins/console_extensions/spec/generated/xpack.watcher.activate_watch.json @@ -5,7 +5,7 @@ "POST" ], "patterns": [ - "_xpack/watcher/watch/{watch_id}/_activate" + "_watcher/watch/{watch_id}/_activate" ], "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/current/watcher-api-activate-watch.html" } diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.watcher.deactivate_watch.json b/x-pack/plugins/console_extensions/spec/generated/xpack.watcher.deactivate_watch.json index dd1bf97787855..2bc0ad6d9df21 100644 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.watcher.deactivate_watch.json +++ b/x-pack/plugins/console_extensions/spec/generated/xpack.watcher.deactivate_watch.json @@ -5,7 +5,7 @@ "POST" ], "patterns": [ - "_xpack/watcher/watch/{watch_id}/_deactivate" + "_watcher/watch/{watch_id}/_deactivate" ], "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/current/watcher-api-deactivate-watch.html" } diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.watcher.delete_watch.json b/x-pack/plugins/console_extensions/spec/generated/xpack.watcher.delete_watch.json index 555e76fb9de2e..5f4ca7c6d464a 100644 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.watcher.delete_watch.json +++ b/x-pack/plugins/console_extensions/spec/generated/xpack.watcher.delete_watch.json @@ -4,7 +4,7 @@ "DELETE" ], "patterns": [ - "_xpack/watcher/watch/{id}" + "_watcher/watch/{id}" ], "documentation": "http://www.elastic.co/guide/en/elasticsearch/reference/current/watcher-api-delete-watch.html" } diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.watcher.execute_watch.json b/x-pack/plugins/console_extensions/spec/generated/xpack.watcher.execute_watch.json index 2c64b9b842038..3697792424e26 100644 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.watcher.execute_watch.json +++ b/x-pack/plugins/console_extensions/spec/generated/xpack.watcher.execute_watch.json @@ -8,8 +8,8 @@ "POST" ], "patterns": [ - "_xpack/watcher/watch/{id}/_execute", - "_xpack/watcher/watch/_execute" + "_watcher/watch/{id}/_execute", + "_watcher/watch/_execute" ], "documentation": "http://www.elastic.co/guide/en/elasticsearch/reference/current/watcher-api-execute-watch.html" } diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.watcher.get_watch.json b/x-pack/plugins/console_extensions/spec/generated/xpack.watcher.get_watch.json index fc2a7d862fd9a..53aedbba8a5b4 100644 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.watcher.get_watch.json +++ b/x-pack/plugins/console_extensions/spec/generated/xpack.watcher.get_watch.json @@ -4,7 +4,7 @@ "GET" ], "patterns": [ - "_xpack/watcher/watch/{id}" + "_watcher/watch/{id}" ], "documentation": "http://www.elastic.co/guide/en/elasticsearch/reference/current/watcher-api-get-watch.html" } diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.watcher.put_watch.json b/x-pack/plugins/console_extensions/spec/generated/xpack.watcher.put_watch.json index bc6951d058f74..ac24ba1599263 100644 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.watcher.put_watch.json +++ b/x-pack/plugins/console_extensions/spec/generated/xpack.watcher.put_watch.json @@ -9,7 +9,7 @@ "POST" ], "patterns": [ - "_xpack/watcher/watch/{id}" + "_watcher/watch/{id}" ], "documentation": "http://www.elastic.co/guide/en/elasticsearch/reference/current/watcher-api-put-watch.html" } diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.watcher.start.json b/x-pack/plugins/console_extensions/spec/generated/xpack.watcher.start.json index 769b9e4e9c941..489965e741944 100644 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.watcher.start.json +++ b/x-pack/plugins/console_extensions/spec/generated/xpack.watcher.start.json @@ -4,7 +4,7 @@ "POST" ], "patterns": [ - "_xpack/watcher/_start" + "_watcher/_start" ], "documentation": "http://www.elastic.co/guide/en/elasticsearch/reference/current/watcher-api-start.html" } diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.watcher.stats.json b/x-pack/plugins/console_extensions/spec/generated/xpack.watcher.stats.json index 1a1df49a44c74..429523ea5d2ec 100644 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.watcher.stats.json +++ b/x-pack/plugins/console_extensions/spec/generated/xpack.watcher.stats.json @@ -4,6 +4,7 @@ "metric": [ "_all", "queued_watches", + "current_watches", "pending_watches" ], "emit_stacktraces": "__flag__" @@ -12,12 +13,13 @@ "GET" ], "patterns": [ - "_xpack/watcher/stats", - "_xpack/watcher/stats/{metrics}" + "_watcher/stats", + "_watcher/stats/{metrics}" ], "url_components": { "metrics": [ "_all", + "current_watches", "pending_watches", "queued_watches" ] diff --git a/x-pack/plugins/console_extensions/spec/generated/xpack.watcher.stop.json b/x-pack/plugins/console_extensions/spec/generated/xpack.watcher.stop.json index 85e9c63158109..4e5b3f4c52dcf 100644 --- a/x-pack/plugins/console_extensions/spec/generated/xpack.watcher.stop.json +++ b/x-pack/plugins/console_extensions/spec/generated/xpack.watcher.stop.json @@ -4,7 +4,7 @@ "POST" ], "patterns": [ - "_xpack/watcher/_stop" + "_watcher/_stop" ], "documentation": "http://www.elastic.co/guide/en/elasticsearch/reference/current/watcher-api-stop.html" } diff --git a/x-pack/plugins/console_extensions/spec/overrides/xpack.ml.get_buckets.json b/x-pack/plugins/console_extensions/spec/overrides/ml.get_buckets.json similarity index 92% rename from x-pack/plugins/console_extensions/spec/overrides/xpack.ml.get_buckets.json rename to x-pack/plugins/console_extensions/spec/overrides/ml.get_buckets.json index cfcb0270b9649..d920ea4db4e95 100644 --- a/x-pack/plugins/console_extensions/spec/overrides/xpack.ml.get_buckets.json +++ b/x-pack/plugins/console_extensions/spec/overrides/ml.get_buckets.json @@ -1,5 +1,5 @@ { - "xpack.ml.get_buckets": { + "ml.get_buckets": { "data_autocomplete_rules": { "desc": { "__one_of": ["true", "false"] }, "exclude_interim": { "__one_of": ["true", "false"] }, diff --git a/x-pack/plugins/console_extensions/spec/overrides/xpack.ml.get_calendar_events.json b/x-pack/plugins/console_extensions/spec/overrides/ml.get_calendar_events.json similarity index 69% rename from x-pack/plugins/console_extensions/spec/overrides/xpack.ml.get_calendar_events.json rename to x-pack/plugins/console_extensions/spec/overrides/ml.get_calendar_events.json index 9af9354cccde3..e2ebea39a6bcf 100644 --- a/x-pack/plugins/console_extensions/spec/overrides/xpack.ml.get_calendar_events.json +++ b/x-pack/plugins/console_extensions/spec/overrides/ml.get_calendar_events.json @@ -1,5 +1,5 @@ { - "xpack.ml.get_calendar_events": { + "ml.get_calendar_events": { "data_autocomplete_rules": { "from": 0, "size": 100 diff --git a/x-pack/plugins/console_extensions/spec/overrides/xpack.ml.get_calendars.json b/x-pack/plugins/console_extensions/spec/overrides/ml.get_calendars.json similarity index 72% rename from x-pack/plugins/console_extensions/spec/overrides/xpack.ml.get_calendars.json rename to x-pack/plugins/console_extensions/spec/overrides/ml.get_calendars.json index 146965574723e..fd961624b8afc 100644 --- a/x-pack/plugins/console_extensions/spec/overrides/xpack.ml.get_calendars.json +++ b/x-pack/plugins/console_extensions/spec/overrides/ml.get_calendars.json @@ -1,5 +1,5 @@ { - "xpack.ml.get_calendars": { + "ml.get_calendars": { "data_autocomplete_rules": { "from": 0, "size": 100 diff --git a/x-pack/plugins/console_extensions/spec/overrides/ml.get_categories.json b/x-pack/plugins/console_extensions/spec/overrides/ml.get_categories.json new file mode 100644 index 0000000000000..c32fb482ee4e4 --- /dev/null +++ b/x-pack/plugins/console_extensions/spec/overrides/ml.get_categories.json @@ -0,0 +1,14 @@ +{ + "ml.get_categories": { + "data_autocomplete_rules": { + "page": { + "__template": { + "from": 0, + "size": 100 + }, + "from": 0, + "size": 100 + } + } + } +} diff --git a/x-pack/plugins/console_extensions/spec/overrides/xpack.ml.get_influencers.json b/x-pack/plugins/console_extensions/spec/overrides/ml.get_influencers.json similarity index 90% rename from x-pack/plugins/console_extensions/spec/overrides/xpack.ml.get_influencers.json rename to x-pack/plugins/console_extensions/spec/overrides/ml.get_influencers.json index df1bc1487dd6b..4f31b3db1007c 100644 --- a/x-pack/plugins/console_extensions/spec/overrides/xpack.ml.get_influencers.json +++ b/x-pack/plugins/console_extensions/spec/overrides/ml.get_influencers.json @@ -1,5 +1,5 @@ { - "xpack.ml.get_influencers": { + "ml.get_influencers": { "data_autocomplete_rules": { "desc": { "__one_of": ["true", "false"] }, "exclude_interim": { "__one_of": ["true", "false"] }, diff --git a/x-pack/plugins/console_extensions/spec/overrides/xpack.ml.get_model_snapshots.json b/x-pack/plugins/console_extensions/spec/overrides/ml.get_model_snapshots.json similarity index 82% rename from x-pack/plugins/console_extensions/spec/overrides/xpack.ml.get_model_snapshots.json rename to x-pack/plugins/console_extensions/spec/overrides/ml.get_model_snapshots.json index 1496851f0c7d9..02a86d39509f8 100644 --- a/x-pack/plugins/console_extensions/spec/overrides/xpack.ml.get_model_snapshots.json +++ b/x-pack/plugins/console_extensions/spec/overrides/ml.get_model_snapshots.json @@ -1,5 +1,5 @@ { - "xpack.ml.get_model_snapshots": { + "ml.get_model_snapshots": { "data_autocomplete_rules": { "desc": [true, false], "end": "", diff --git a/x-pack/plugins/console_extensions/spec/overrides/xpack.ml.get_overall_buckets.json b/x-pack/plugins/console_extensions/spec/overrides/ml.get_overall_buckets.json similarity index 88% rename from x-pack/plugins/console_extensions/spec/overrides/xpack.ml.get_overall_buckets.json rename to x-pack/plugins/console_extensions/spec/overrides/ml.get_overall_buckets.json index cb41f5ef78aaa..312c44419ba90 100644 --- a/x-pack/plugins/console_extensions/spec/overrides/xpack.ml.get_overall_buckets.json +++ b/x-pack/plugins/console_extensions/spec/overrides/ml.get_overall_buckets.json @@ -1,5 +1,5 @@ { - "xpack.ml.get_overall_buckets": { + "ml.get_overall_buckets": { "data_autocomplete_rules": { "allow_no_jobs": { "__one_of": ["true", "false"] }, "bucket_span": "", diff --git a/x-pack/plugins/console_extensions/spec/overrides/xpack.ml.get_records.json b/x-pack/plugins/console_extensions/spec/overrides/ml.get_records.json similarity index 91% rename from x-pack/plugins/console_extensions/spec/overrides/xpack.ml.get_records.json rename to x-pack/plugins/console_extensions/spec/overrides/ml.get_records.json index 16691960696b5..d68dabaeac367 100644 --- a/x-pack/plugins/console_extensions/spec/overrides/xpack.ml.get_records.json +++ b/x-pack/plugins/console_extensions/spec/overrides/ml.get_records.json @@ -1,5 +1,5 @@ { - "xpack.ml.get_records": { + "ml.get_records": { "data_autocomplete_rules": { "desc": { "__one_of": ["true", "false"] }, "exclude_interim": { "__one_of": ["true", "false"] }, diff --git a/x-pack/plugins/console_extensions/spec/overrides/xpack.ml.post_calendar_events.json b/x-pack/plugins/console_extensions/spec/overrides/ml.post_calendar_events.json similarity index 64% rename from x-pack/plugins/console_extensions/spec/overrides/xpack.ml.post_calendar_events.json rename to x-pack/plugins/console_extensions/spec/overrides/ml.post_calendar_events.json index 05e3e3697df3c..543a2758ff03a 100644 --- a/x-pack/plugins/console_extensions/spec/overrides/xpack.ml.post_calendar_events.json +++ b/x-pack/plugins/console_extensions/spec/overrides/ml.post_calendar_events.json @@ -1,5 +1,5 @@ { - "xpack.ml.post_calendar_events": { + "ml.post_calendar_events": { "data_autocomplete_rules": { "events": [{}] } diff --git a/x-pack/plugins/console_extensions/spec/overrides/xpack.ml.put_calendar.json b/x-pack/plugins/console_extensions/spec/overrides/ml.put_calendar.json similarity index 71% rename from x-pack/plugins/console_extensions/spec/overrides/xpack.ml.put_calendar.json rename to x-pack/plugins/console_extensions/spec/overrides/ml.put_calendar.json index d0ca83a2fd2af..9bfddb49f84c6 100644 --- a/x-pack/plugins/console_extensions/spec/overrides/xpack.ml.put_calendar.json +++ b/x-pack/plugins/console_extensions/spec/overrides/ml.put_calendar.json @@ -1,5 +1,5 @@ { - "xpack.ml.put_calendar": { + "ml.put_calendar": { "data_autocomplete_rules": { "description": "" } diff --git a/x-pack/plugins/console_extensions/spec/overrides/xpack.ml.put_datafeed.json b/x-pack/plugins/console_extensions/spec/overrides/ml.put_datafeed.json similarity index 90% rename from x-pack/plugins/console_extensions/spec/overrides/xpack.ml.put_datafeed.json rename to x-pack/plugins/console_extensions/spec/overrides/ml.put_datafeed.json index a3b50af4db036..bb42e485a621e 100644 --- a/x-pack/plugins/console_extensions/spec/overrides/xpack.ml.put_datafeed.json +++ b/x-pack/plugins/console_extensions/spec/overrides/ml.put_datafeed.json @@ -1,5 +1,5 @@ { - "xpack.ml.put_datafeed": { + "ml.put_datafeed": { "data_autocomplete_rules": { "aggregations": {}, "chunking_config": {}, diff --git a/x-pack/plugins/console_extensions/spec/overrides/xpack.ml.put_job.json b/x-pack/plugins/console_extensions/spec/overrides/ml.put_job.json similarity index 94% rename from x-pack/plugins/console_extensions/spec/overrides/xpack.ml.put_job.json rename to x-pack/plugins/console_extensions/spec/overrides/ml.put_job.json index aee010691b964..ec62d7646aefc 100644 --- a/x-pack/plugins/console_extensions/spec/overrides/xpack.ml.put_job.json +++ b/x-pack/plugins/console_extensions/spec/overrides/ml.put_job.json @@ -1,5 +1,5 @@ { - "xpack.ml.put_job": { + "ml.put_job": { "data_autocomplete_rules": { "analysis_config": {}, "background_persist_interval": "", diff --git a/x-pack/plugins/console_extensions/spec/overrides/xpack.ml.revert_model_snapshot.json b/x-pack/plugins/console_extensions/spec/overrides/ml.revert_model_snapshot.json similarity index 75% rename from x-pack/plugins/console_extensions/spec/overrides/xpack.ml.revert_model_snapshot.json rename to x-pack/plugins/console_extensions/spec/overrides/ml.revert_model_snapshot.json index 806b326271bad..00f69cd0893f2 100644 --- a/x-pack/plugins/console_extensions/spec/overrides/xpack.ml.revert_model_snapshot.json +++ b/x-pack/plugins/console_extensions/spec/overrides/ml.revert_model_snapshot.json @@ -1,5 +1,5 @@ { - "xpack.ml.revert_model_snapshot": { + "ml.revert_model_snapshot": { "data_autocomplete_rules": { "delete_intervening_results": { "__one_of": ["true", "false"] } } diff --git a/x-pack/plugins/console_extensions/spec/overrides/xpack.ml.update_datafeed.json b/x-pack/plugins/console_extensions/spec/overrides/ml.update_datafeed.json similarity index 89% rename from x-pack/plugins/console_extensions/spec/overrides/xpack.ml.update_datafeed.json rename to x-pack/plugins/console_extensions/spec/overrides/ml.update_datafeed.json index f096707690eea..13616b23dbbbf 100644 --- a/x-pack/plugins/console_extensions/spec/overrides/xpack.ml.update_datafeed.json +++ b/x-pack/plugins/console_extensions/spec/overrides/ml.update_datafeed.json @@ -1,5 +1,5 @@ { - "xpack.ml.update_datafeed": { + "ml.update_datafeed": { "data_autocomplete_rules": { "aggregations": {}, "chunking_config": {}, diff --git a/x-pack/plugins/console_extensions/spec/overrides/xpack.ml.update_job.json b/x-pack/plugins/console_extensions/spec/overrides/ml.update_job.json similarity index 93% rename from x-pack/plugins/console_extensions/spec/overrides/xpack.ml.update_job.json rename to x-pack/plugins/console_extensions/spec/overrides/ml.update_job.json index 562fdef07c790..b95259c017a67 100644 --- a/x-pack/plugins/console_extensions/spec/overrides/xpack.ml.update_job.json +++ b/x-pack/plugins/console_extensions/spec/overrides/ml.update_job.json @@ -1,5 +1,5 @@ { - "xpack.ml.update_job": { + "ml.update_job": { "data_autocomplete_rules": { "analysis_limits": {}, "background_persist_interval": "", diff --git a/x-pack/plugins/console_extensions/spec/overrides/xpack.ml.update_model_snapshot.json b/x-pack/plugins/console_extensions/spec/overrides/ml.update_model_snapshot.json similarity index 72% rename from x-pack/plugins/console_extensions/spec/overrides/xpack.ml.update_model_snapshot.json rename to x-pack/plugins/console_extensions/spec/overrides/ml.update_model_snapshot.json index bbf98c6ac05e7..c5cb3e0e90a63 100644 --- a/x-pack/plugins/console_extensions/spec/overrides/xpack.ml.update_model_snapshot.json +++ b/x-pack/plugins/console_extensions/spec/overrides/ml.update_model_snapshot.json @@ -1,5 +1,5 @@ { - "xpack.ml.update_model_snapshot": { + "ml.update_model_snapshot": { "data_autocomplete_rules": { "description": "", "retain": [true, false] diff --git a/x-pack/plugins/console_extensions/spec/overrides/xpack.ml.get_categories.json b/x-pack/plugins/console_extensions/spec/overrides/xpack.ml.get_categories.json deleted file mode 100644 index b3a46b02edefc..0000000000000 --- a/x-pack/plugins/console_extensions/spec/overrides/xpack.ml.get_categories.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "xpack.ml.get_categories": { - "data_autocomplete_rules": { - "page": { - "__template": { - "from": 0, - "size": 100 - }, - "from": 0, - "size": 100 - } - }, - "patterns": [ - "_xpack/ml/anomaly_detectors/{job_id}/results/categories/{category_id}", - "_xpack/ml/anomaly_detectors/{job_id}/results/categories" - ] - } -} diff --git a/x-pack/plugins/console_extensions/spec/overrides/xpack.rollup.get_jobs.json b/x-pack/plugins/console_extensions/spec/overrides/xpack.rollup.get_jobs.json deleted file mode 100644 index 06a89d35819f8..0000000000000 --- a/x-pack/plugins/console_extensions/spec/overrides/xpack.rollup.get_jobs.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "xpack.rollup.get_jobs": { - "patterns": [ - "_xpack/rollup/job/{id}" - ] - } -} diff --git a/x-pack/plugins/cross_cluster_replication/public/extend_index_management/index.js b/x-pack/plugins/cross_cluster_replication/public/extend_index_management/index.js index 4ee2e8908b708..3c7aa90b4073d 100644 --- a/x-pack/plugins/cross_cluster_replication/public/extend_index_management/index.js +++ b/x-pack/plugins/cross_cluster_replication/public/extend_index_management/index.js @@ -17,7 +17,8 @@ export const followerBadgeExtension = { label: i18n.translate('xpack.crossClusterReplication.indexMgmtBadge.followerLabel', { defaultMessage: 'Follower', }), - color: 'default' + color: 'default', + filterExpression: 'isFollowerIndex:true' }; addBadgeExtension(followerBadgeExtension); diff --git a/x-pack/plugins/gis/common/constants.js b/x-pack/plugins/gis/common/constants.js index e76ce51bb37df..2e8fb14c2e875 100644 --- a/x-pack/plugins/gis/common/constants.js +++ b/x-pack/plugins/gis/common/constants.js @@ -9,3 +9,5 @@ export const GIS_API_PATH = 'api/gis'; export const DECIMAL_DEGREES_PRECISION = 5; // meters precision export const ZOOM_PRECISION = 2; + +export const DEFAULT_EMS_TILE_LAYER = 'road_map'; diff --git a/x-pack/plugins/gis/public/_main.scss b/x-pack/plugins/gis/public/_main.scss index 5e8b75daad1c6..f7794072b8e74 100644 --- a/x-pack/plugins/gis/public/_main.scss +++ b/x-pack/plugins/gis/public/_main.scss @@ -8,12 +8,6 @@ overflow: hidden; } -@include euiBreakpoint('xs') { - #gis-plugin { - height: calc(100vh - #{$euiHeaderChildSizeMobile}); - } -} - #react-gis-root { flex-grow: 1; display: flex; diff --git a/x-pack/plugins/gis/public/actions/store_actions.js b/x-pack/plugins/gis/public/actions/store_actions.js index c7a2571695393..8c8d8c71f06ff 100644 --- a/x-pack/plugins/gis/public/actions/store_actions.js +++ b/x-pack/plugins/gis/public/actions/store_actions.js @@ -16,7 +16,6 @@ import { getMapReady, getWaitingForMapReadyLayerListRaw, } from '../selectors/map_selectors'; -import { timeService } from '../kibana_services'; export const SET_SELECTED_LAYER = 'SET_SELECTED_LAYER'; export const UPDATE_LAYER_ORDER = 'UPDATE_LAYER_ORDER'; @@ -35,7 +34,6 @@ export const LAYER_DATA_LOAD_STARTED = 'LAYER_DATA_LOAD_STARTED'; export const LAYER_DATA_LOAD_ENDED = 'LAYER_DATA_LOAD_ENDED'; export const LAYER_DATA_LOAD_ERROR = 'LAYER_DATA_LOAD_ERROR'; export const SET_JOINS = 'SET_JOINS'; -export const SET_TIME_FILTERS = 'SET_TIME_FILTERS'; export const SET_QUERY = 'SET_QUERY'; export const TRIGGER_REFRESH_TIMER = 'TRIGGER_REFRESH_TIMER'; export const UPDATE_LAYER_PROP = 'UPDATE_LAYER_PROP'; @@ -418,43 +416,17 @@ export function removeLayer(id) { } export function setMeta(metaJson) { - return async dispatch => { - dispatch({ - type: SET_META, - meta: metaJson - }); - }; -} - -export function setTimeFiltersToKbnGlobalTime() { - return (dispatch) => { - dispatch(setTimeFilters(timeService.getTime())); - }; -} - -export function setTimeFilters({ from, to }) { - return async (dispatch, getState) => { - dispatch({ - type: SET_TIME_FILTERS, - from, - to, - }); - - // Update Kibana global time - const kbnTime = timeService.getTime(); - if ((to && to !== kbnTime.to) || (from && from !== kbnTime.from)) { - timeService.setTime({ from, to }); - } - - const dataFilters = getDataFilters(getState()); - await syncDataForAllLayers(getState, dispatch, dataFilters); + return { + type: SET_META, + meta: metaJson }; } -export function setQuery({ query }) { +export function setQuery({ query, timeFilters }) { return async (dispatch, getState) => { dispatch({ type: SET_QUERY, + timeFilters, query: { ...query, // ensure query changes to trigger re-fetch even when query is the same because "Refresh" clicked @@ -468,21 +440,10 @@ export function setQuery({ query }) { } export function setRefreshConfig({ isPaused, interval }) { - return async (dispatch) => { - dispatch({ - type: SET_REFRESH_CONFIG, - isPaused, - interval, - }); - - // Update Kibana global refresh - const kbnRefresh = timeService.getRefreshInterval(); - if (isPaused !== kbnRefresh.pause || interval !== kbnRefresh.value) { - timeService.setRefreshInterval({ - pause: isPaused, - value: interval, - }); - } + return { + type: SET_REFRESH_CONFIG, + isPaused, + interval, }; } diff --git a/x-pack/plugins/gis/public/angular/get_initial_layers.js b/x-pack/plugins/gis/public/angular/get_initial_layers.js index 385114d29eef6..a7cc6e77fa317 100644 --- a/x-pack/plugins/gis/public/angular/get_initial_layers.js +++ b/x-pack/plugins/gis/public/angular/get_initial_layers.js @@ -6,12 +6,25 @@ import _ from 'lodash'; import { KibanaTilemapSource } from '../shared/layers/sources/kibana_tilemap_source'; import { EMSTMSSource } from '../shared/layers/sources/ems_tms_source'; +import { isMetaDataLoaded, getDataSourcesSync } from '../meta'; +import { DEFAULT_EMS_TILE_LAYER } from '../../common/constants'; + +export function getInitialLayers(savedMapLayerListJSON) { -export function getInitialLayers(savedMapLayerListJSON, dataSources) { if (savedMapLayerListJSON) { return JSON.parse(savedMapLayerListJSON); } + if (!isMetaDataLoaded()) { + const descriptor = EMSTMSSource.createDescriptor(DEFAULT_EMS_TILE_LAYER); + const source = new EMSTMSSource(descriptor); + const layer = source.createDefaultLayer(); + return [ + layer.toLayerDescriptor() + ]; + } + + const dataSources = getDataSourcesSync(); const kibanaTilemapUrl = _.get(dataSources, 'kibana.tilemap.url'); if (kibanaTilemapUrl) { const sourceDescriptor = KibanaTilemapSource.createDescriptor(kibanaTilemapUrl); @@ -32,7 +45,5 @@ export function getInitialLayers(savedMapLayerListJSON, dataSources) { ]; } - // TODO display (or throw) warning that no tile layers are available and map.tilemap needs to be configured - // because EMS is unreachable or has been turned off on purpose. return []; } diff --git a/x-pack/plugins/gis/public/angular/get_initial_layers.test.js b/x-pack/plugins/gis/public/angular/get_initial_layers.test.js index 47f610d8cf8db..9554dc54bda00 100644 --- a/x-pack/plugins/gis/public/angular/get_initial_layers.test.js +++ b/x-pack/plugins/gis/public/angular/get_initial_layers.test.js @@ -4,6 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ +jest.mock('../meta', () => { + return {}; +}); + import { getInitialLayers } from './get_initial_layers'; const mockKibanaDataSource = { @@ -28,22 +32,65 @@ describe('Saved object has layer list', () => { } ]; const layerListJSON = JSON.stringify(layerListFromSavedObject); - const dataSources = { - kibana: mockKibanaDataSource, - ems: mockEmsDataSource - }; - expect(getInitialLayers(layerListJSON, dataSources)).toEqual(layerListFromSavedObject); + expect((getInitialLayers(layerListJSON))).toEqual(layerListFromSavedObject); }); }); + + describe('Saved object does not have layer list', () => { + + beforeEach(() => { + require('../meta').isMetaDataLoaded = () => { + return true; + }; + }); + + function mockDataSourceResponse(dataSources) { + require('../meta').getDataSourcesSync = () => { + return dataSources; + }; + require('../meta').isMetaDataLoaded = () => { + return true; + }; + } + + it('should get the default EMS layer when metadata has not loaded yet', () => { + mockDataSourceResponse(); + require('../meta').isMetaDataLoaded = () => { + return false; + }; + const layers = getInitialLayers(null); + expect(layers).toEqual([{ + "alpha": 1, + "dataRequests": [], + "id": layers[0].id, + "label": null, + "maxZoom": 24, + "minZoom": 0, + "sourceDescriptor": { + "type": "EMS_TMS", + "id": "road_map", + }, + "style": { + "properties": {}, + "type": "TILE", + }, + "temporary": false, + "type": "TILE", + "visible": true, + }]); + }); + + it('Should get initial layer from Kibana tilemap data source when Kibana tilemap is configured ', () => { - const dataSources = { + + mockDataSourceResponse({ kibana: mockKibanaDataSource, ems: mockEmsDataSource - }; + }); - const layers = getInitialLayers(null, dataSources); + const layers = getInitialLayers(null); expect(layers).toEqual([{ "alpha": 1, dataRequests: [], @@ -70,8 +117,9 @@ describe('Saved object does not have layer list', () => { const dataSources = { ems: mockEmsDataSource }; + mockDataSourceResponse(dataSources); - const layers = getInitialLayers(null, dataSources); + const layers = getInitialLayers(null); expect(layers).toEqual([{ "alpha": 1, dataRequests: [], @@ -100,10 +148,12 @@ describe('Saved object does not have layer list', () => { tms: [] } }; - expect(getInitialLayers(null, dataSources)).toEqual([]); + mockDataSourceResponse(dataSources); + expect((getInitialLayers(null))).toEqual([]); }); it('Should return empty list when no dataSoures are provided', () => { - expect(getInitialLayers(null, null)).toEqual([]); + mockDataSourceResponse(null); + expect((getInitialLayers(null))).toEqual([]); }); }); diff --git a/x-pack/plugins/gis/public/angular/get_initial_query.js b/x-pack/plugins/gis/public/angular/get_initial_query.js new file mode 100644 index 0000000000000..e30e7e4ad9f58 --- /dev/null +++ b/x-pack/plugins/gis/public/angular/get_initial_query.js @@ -0,0 +1,32 @@ +/* + * 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 chrome from 'ui/chrome'; + +const settings = chrome.getUiSettingsClient(); + +export function getInitialQuery({ + mapStateJSON, + appState = {}, + userQueryLanguage, +}) { + + if (appState.query) { + return appState.query; + } + + if (mapStateJSON) { + const mapState = JSON.parse(mapStateJSON); + if (mapState.query) { + return mapState.query; + } + } + + return { + query: '', + language: userQueryLanguage || settings.get('search:queryLanguage') + }; +} diff --git a/x-pack/plugins/gis/public/angular/get_initial_refresh_config.js b/x-pack/plugins/gis/public/angular/get_initial_refresh_config.js new file mode 100644 index 0000000000000..5d042028c1ef8 --- /dev/null +++ b/x-pack/plugins/gis/public/angular/get_initial_refresh_config.js @@ -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. + */ +import chrome from 'ui/chrome'; + +const uiSettings = chrome.getUiSettingsClient(); + +export function getInitialRefreshConfig({ + mapStateJSON, + globalState = {}, +}) { + + if (mapStateJSON) { + const mapState = JSON.parse(mapStateJSON); + if (mapState.refreshConfig) { + return mapState.refreshConfig; + } + } + + const defaultRefreshConfig = uiSettings.get('timepicker:refreshIntervalDefaults'); + const refreshInterval = { ...defaultRefreshConfig, ...globalState.refreshInterval }; + return { + isPaused: refreshInterval.pause, + interval: refreshInterval.value, + }; +} diff --git a/x-pack/plugins/gis/public/angular/get_initial_time_filters.js b/x-pack/plugins/gis/public/angular/get_initial_time_filters.js new file mode 100644 index 0000000000000..8ce57e0d0991b --- /dev/null +++ b/x-pack/plugins/gis/public/angular/get_initial_time_filters.js @@ -0,0 +1,24 @@ +/* + * 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 chrome from 'ui/chrome'; + +const uiSettings = chrome.getUiSettingsClient(); + +export function getInitialTimeFilters({ + mapStateJSON, + globalState = {}, +}) { + + if (mapStateJSON) { + const mapState = JSON.parse(mapStateJSON); + if (mapState.timeFilters) { + return mapState.timeFilters; + } + } + + const defaultTime = uiSettings.get('timepicker:timeDefaults'); + return { ...defaultTime, ...globalState.time }; +} diff --git a/x-pack/plugins/gis/public/angular/map.html b/x-pack/plugins/gis/public/angular/map.html index 34f953f0525ea..8bffe59548dcd 100644 --- a/x-pack/plugins/gis/public/angular/map.html +++ b/x-pack/plugins/gis/public/angular/map.html @@ -27,6 +27,12 @@ app-name="'maps'" on-submit="updateQueryAndDispatch" index-patterns="indexPatterns" + show-date-picker="showDatePicker" + date-range-from="time.from" + date-range-to="time.to" + is-refresh-paused="refreshConfig.isPaused" + refresh-interval="refreshConfig.interval" + on-refresh-change="onRefreshChange" > diff --git a/x-pack/plugins/gis/public/angular/map_controller.js b/x-pack/plugins/gis/public/angular/map_controller.js index 6faae6a414e21..d9d6d12e8933e 100644 --- a/x-pack/plugins/gis/public/angular/map_controller.js +++ b/x-pack/plugins/gis/public/angular/map_controller.js @@ -14,54 +14,108 @@ import { getStore } from '../store/store'; import { GisMap } from '../components/gis_map'; import { setSelectedLayer, - setTimeFilters, setRefreshConfig, setGotoWithCenter, replaceLayerList, setQuery, } from '../actions/store_actions'; import { updateFlyout, FLYOUT_STATE } from '../store/ui'; -import { getDataSources, getUniqueIndexPatternIds } from '../selectors/map_selectors'; +import { getUniqueIndexPatternIds } from '../selectors/map_selectors'; import { Inspector } from 'ui/inspector'; import { inspectorAdapters, indexPatternService } from '../kibana_services'; import { SavedObjectSaveModal } from 'ui/saved_objects/components/saved_object_save_modal'; import { showSaveModal } from 'ui/saved_objects/show_saved_object_save_modal'; import { toastNotifications } from 'ui/notify'; import { getInitialLayers } from './get_initial_layers'; +import { getInitialQuery } from './get_initial_query'; +import { getInitialTimeFilters } from './get_initial_time_filters'; +import { getInitialRefreshConfig } from './get_initial_refresh_config'; const REACT_ANCHOR_DOM_ELEMENT_ID = 'react-gis-root'; -const DEFAULT_QUERY_LANGUAGE = 'kuery'; + const app = uiModules.get('app/gis', []); -app.controller('GisMapController', ($scope, $route, config, kbnUrl, localStorage, AppState) => { +app.controller('GisMapController', ($scope, $route, config, kbnUrl, localStorage, AppState, globalState) => { const savedMap = $scope.map = $route.current.locals.map; let unsubscribe; inspectorAdapters.requests.reset(); + $scope.$listen(globalState, 'fetch_with_changes', (diff) => { + if (diff.includes('time')) { + $scope.updateQueryAndDispatch({ query: $scope.query, dateRange: globalState.time }); + } + if (diff.includes('refreshInterval')) { + $scope.onRefreshChange({ isPaused: globalState.pause, refreshInterval: globalState.value }); + } + }); + const $state = new AppState(); $scope.$listen($state, 'fetch_with_changes', function (diff) { if (diff.includes('query')) { - $scope.updateQueryAndDispatch($state.query); + $scope.updateQueryAndDispatch({ query: $state.query, dateRange: $scope.time }); } }); - $scope.query = {}; + + function syncAppAndGlobalState() { + $scope.$evalAsync(() => { + $state.query = $scope.query; + $state.save(); + globalState.time = $scope.time; + globalState.refreshInterval = { + pause: $scope.refreshConfig.isPaused, + value: $scope.refreshConfig.interval, + }; + globalState.save(); + }); + } + + $scope.query = getInitialQuery({ + mapStateJSON: savedMap.mapStateJSON, + appState: $state, + userQueryLanguage: localStorage.get('kibana.userQueryLanguage') + }); + $scope.time = getInitialTimeFilters({ + mapStateJSON: savedMap.mapStateJSON, + globalState: globalState, + }); + $scope.refreshConfig = getInitialRefreshConfig({ + mapStateJSON: savedMap.mapStateJSON, + globalState: globalState, + }); + syncAppAndGlobalState(); + $scope.indexPatterns = []; - $scope.updateQueryAndDispatch = function (newQuery) { - $scope.query = newQuery; + $scope.updateQueryAndDispatch = function ({ dateRange, query }) { + $scope.query = query; + $scope.time = dateRange; getStore().then(store => { // ignore outdated query - if ($scope.query !== newQuery) { + if ($scope.query !== query && $scope.time !== dateRange) { return; } - store.dispatch(setQuery({ query: $scope.query })); + store.dispatch(setQuery({ query: $scope.query, timeFilters: $scope.time })); - // update appState - $state.query = $scope.query; - $state.save(); + syncAppAndGlobalState(); + }); + }; + $scope.onRefreshChange = function ({ isPaused, refreshInterval }) { + $scope.refreshConfig = { + isPaused, + interval: refreshInterval ? refreshInterval : $scope.refreshConfig.interval + }; + getStore().then(store => { + // ignore outdated + if ($scope.refreshConfig.isPaused !== isPaused && $scope.refreshConfig.interval !== refreshInterval) { + return; + } + + store.dispatch(setRefreshConfig($scope.refreshConfig)); + + syncAppAndGlobalState(); }); }; @@ -76,36 +130,20 @@ app.controller('GisMapController', ($scope, $route, config, kbnUrl, localStorage }); // sync store with savedMap mapState - let queryFromSavedObject; if (savedMap.mapStateJSON) { const mapState = JSON.parse(savedMap.mapStateJSON); - queryFromSavedObject = mapState.query; - const timeFilters = mapState.timeFilters ? mapState.timeFilters : timefilter.getTime(); - store.dispatch(setTimeFilters(timeFilters)); store.dispatch(setGotoWithCenter({ lat: mapState.center.lat, lon: mapState.center.lon, zoom: mapState.zoom, })); - if (mapState.refreshConfig) { - store.dispatch(setRefreshConfig(mapState.refreshConfig)); - } } - const layerList = getInitialLayers(savedMap.layerListJSON, getDataSources(store.getState())); + const layerList = getInitialLayers(savedMap.layerListJSON); store.dispatch(replaceLayerList(layerList)); - // Initialize query, syncing appState and store - if ($state.query) { - $scope.updateQueryAndDispatch($state.query); - } else if (queryFromSavedObject) { - $scope.updateQueryAndDispatch(queryFromSavedObject); - } else { - $scope.updateQueryAndDispatch({ - query: '', - language: localStorage.get('kibana.userQueryLanguage') || DEFAULT_QUERY_LANGUAGE - }); - } + store.dispatch(setRefreshConfig($scope.refreshConfig)); + store.dispatch(setQuery({ query: $scope.query, timeFilters: $scope.time })); const root = document.getElementById(REACT_ANCHOR_DOM_ELEMENT_ID); render( @@ -136,9 +174,7 @@ app.controller('GisMapController', ($scope, $route, config, kbnUrl, localStorage } function handleStoreChanges(store) { - const state = store.getState(); - - const nextIndexPatternIds = getUniqueIndexPatternIds(state); + const nextIndexPatternIds = getUniqueIndexPatternIds(store.getState()); if (nextIndexPatternIds !== prevIndexPatternIds) { prevIndexPatternIds = nextIndexPatternIds; updateIndexPatterns(nextIndexPatternIds); @@ -197,6 +233,10 @@ app.controller('GisMapController', ($scope, $route, config, kbnUrl, localStorage return { id }; } + // Hide angular timepicer/refresh UI from top nav + timefilter.disableTimeRangeSelector(); + timefilter.disableAutoRefreshSelector(); + $scope.showDatePicker = true; // used by query-bar directive to enable timepikcer in query bar $scope.topNavMenu = [{ key: 'inspect', description: 'Open Inspector', @@ -238,6 +278,4 @@ app.controller('GisMapController', ($scope, $route, config, kbnUrl, localStorage showSaveModal(saveModal); } }]; - timefilter.enableTimeRangeSelector(); - timefilter.enableAutoRefreshSelector(); }); diff --git a/x-pack/plugins/gis/public/components/gis_map/index.js b/x-pack/plugins/gis/public/components/gis_map/index.js index b0e31b7e2f470..cff886a1ff123 100644 --- a/x-pack/plugins/gis/public/components/gis_map/index.js +++ b/x-pack/plugins/gis/public/components/gis_map/index.js @@ -7,26 +7,22 @@ import { connect } from 'react-redux'; import { GisMap } from './view'; import { getFlyoutDisplay, FLYOUT_STATE } from '../../store/ui'; -import { - setTimeFiltersToKbnGlobalTime, - triggerRefreshTimer, - setRefreshConfig -} from '../../actions/store_actions'; +import { triggerRefreshTimer } from '../../actions/store_actions'; +import { getRefreshConfig } from '../../selectors/map_selectors'; function mapStateToProps(state = {}) { const flyoutDisplay = getFlyoutDisplay(state); return { layerDetailsVisible: flyoutDisplay === FLYOUT_STATE.LAYER_PANEL, addLayerVisible: flyoutDisplay === FLYOUT_STATE.ADD_LAYER_WIZARD, - noFlyoutVisible: flyoutDisplay === FLYOUT_STATE.NONE + noFlyoutVisible: flyoutDisplay === FLYOUT_STATE.NONE, + refreshConfig: getRefreshConfig(state), }; } function mapDispatchToProps(dispatch) { return { - setTimeFiltersToKbnGlobalTime: () => dispatch(setTimeFiltersToKbnGlobalTime()), triggerRefreshTimer: () => dispatch(triggerRefreshTimer()), - setRefreshConfig: (({ isPaused, interval }) => dispatch(setRefreshConfig({ isPaused, interval }))), }; } diff --git a/x-pack/plugins/gis/public/components/gis_map/view.js b/x-pack/plugins/gis/public/components/gis_map/view.js index d3a08630e970c..2abad91c4b974 100644 --- a/x-pack/plugins/gis/public/components/gis_map/view.js +++ b/x-pack/plugins/gis/public/components/gis_map/view.js @@ -12,39 +12,41 @@ import { AddLayerPanel } from '../layer_addpanel/index'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { Toasts } from '../toasts'; -import { timeService } from '../../kibana_services'; - export class GisMap extends Component { componentDidMount() { - timeService.on('timeUpdate', this.props.setTimeFiltersToKbnGlobalTime); - timeService.on('refreshIntervalUpdate', this.setRefreshTimer); + this.setRefreshTimer(); + } + + componentDidUpdate() { this.setRefreshTimer(); } componentWillUnmount() { - timeService.off('timeUpdate', this.props.setTimeFiltersToKbnGlobalTime); - timeService.off('refreshIntervalUpdate', this.setRefreshTimer); this.clearRefreshTimer(); } setRefreshTimer = () => { + const { isPaused, interval } = this.props.refreshConfig; + + if (this.isPaused === isPaused && this.interval === interval) { + // refreshConfig is the same, nothing to do + return; + } + + this.isPaused = isPaused; + this.interval = interval; + this.clearRefreshTimer(); - const { value, pause } = timeService.getRefreshInterval(); - if (!pause && value > 0) { + if (!isPaused && interval > 0) { this.refreshTimerId = setInterval( () => { this.props.triggerRefreshTimer(); }, - value + interval ); } - - this.props.setRefreshConfig({ - isPaused: pause, - interval: value, - }); } clearRefreshTimer = () => { diff --git a/x-pack/plugins/gis/public/components/layer_addpanel/index.js b/x-pack/plugins/gis/public/components/layer_addpanel/index.js index 525843f3481f9..aa45eacf5b43f 100644 --- a/x-pack/plugins/gis/public/components/layer_addpanel/index.js +++ b/x-pack/plugins/gis/public/components/layer_addpanel/index.js @@ -8,7 +8,7 @@ import { connect } from 'react-redux'; import { AddLayerPanel } from './view'; import { getFlyoutDisplay, updateFlyout, FLYOUT_STATE } from '../../store/ui'; -import { getTemporaryLayers, getDataSources } from "../../selectors/map_selectors"; +import { getTemporaryLayers } from "../../selectors/map_selectors"; import { addLayer, removeLayer, @@ -19,14 +19,12 @@ import _ from 'lodash'; function mapStateToProps(state = {}) { - const dataSourceMeta = getDataSources(state); function isLoading() { const tmp = getTemporaryLayers(state); return tmp.some((layer) => layer.isLayerLoading()); } return { flyoutVisible: getFlyoutDisplay(state) !== FLYOUT_STATE.NONE, - dataSourcesMeta: dataSourceMeta, layerLoading: isLoading(), temporaryLayers: !_.isEmpty(getTemporaryLayers(state)) }; diff --git a/x-pack/plugins/gis/public/components/layer_addpanel/view.js b/x-pack/plugins/gis/public/components/layer_addpanel/view.js index 2149610054034..f92194c9ce593 100644 --- a/x-pack/plugins/gis/public/components/layer_addpanel/view.js +++ b/x-pack/plugins/gis/public/components/layer_addpanel/view.js @@ -108,8 +108,7 @@ export class AddLayerPanel extends Component { _renderSourceEditor() { const editorProperties = { - onPreviewSource: this._previewLayer, - dataSourcesMeta: this.props.dataSourcesMeta + onPreviewSource: this._previewLayer }; const Source = ALL_SOURCES.find((Source) => { diff --git a/x-pack/plugins/gis/public/meta.js b/x-pack/plugins/gis/public/meta.js new file mode 100644 index 0000000000000..40c0f03c2a79a --- /dev/null +++ b/x-pack/plugins/gis/public/meta.js @@ -0,0 +1,60 @@ +/* + * 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 { GIS_API_PATH } from '../common/constants'; +import _ from 'lodash'; + +const GIS_API_RELATIVE = `../${GIS_API_PATH}`; + +let meta = null; +let isLoaded = false; +export async function getDataSources() { + if (!meta) { + meta = new Promise(async (resolve, reject) => { + try { + const meta = await fetch(`${GIS_API_RELATIVE}/meta`); + const metaJson = await meta.json(); + isLoaded = true; + resolve(metaJson.data_sources); + } catch(e) { + reject(e); + } + }); + } + return meta; +} + +export function getDataSourcesSync() { + if (!isLoaded) { + throw new Error('Metadata is not loaded yet. Use isMetadataLoaded first before calling this function.'); + } + return meta; +} + +export function isMetaDataLoaded() { + return isLoaded; +} + +export async function getEmsVectorFilesMeta() { + const dataSource = await getDataSources(); + return _.get(dataSource, 'ems.file', []); +} + +export async function getEmsTMSServices() { + const dataSource = await getDataSources(); + return _.get(dataSource, 'ems.tms', []); +} + +export async function getKibanaRegionList() { + const dataSource = await getDataSources(); + return _.get(dataSource, 'kibana.regionmap', []); +} + +export async function getKibanaTileMap() { + const dataSource = await getDataSources(); + return _.get(dataSource, 'kibana.tilemap', []); +} diff --git a/x-pack/plugins/gis/public/selectors/map_selectors.js b/x-pack/plugins/gis/public/selectors/map_selectors.js index 6935d7b66fb83..f3274d0e8826f 100644 --- a/x-pack/plugins/gis/public/selectors/map_selectors.js +++ b/x-pack/plugins/gis/public/selectors/map_selectors.js @@ -15,8 +15,8 @@ import { HeatmapStyle } from '../shared/layers/styles/heatmap_style'; import { TileStyle } from '../shared/layers/styles/tile_style'; import { timefilter } from 'ui/timefilter'; -function createLayerInstance(layerDescriptor, dataSources) { - const source = createSourceInstance(layerDescriptor.sourceDescriptor, dataSources); +function createLayerInstance(layerDescriptor) { + const source = createSourceInstance(layerDescriptor.sourceDescriptor); const style = createStyleInstance(layerDescriptor.style); switch (layerDescriptor.type) { case TileLayer.type: @@ -30,21 +30,14 @@ function createLayerInstance(layerDescriptor, dataSources) { } } -function createSourceInstance(sourceDescriptor, dataSources) { - - const dataMeta = { - emsTmsServices: _.get(dataSources, 'ems.tms', []), - emsFileLayers: _.get(dataSources, 'ems.file', []), - ymlFileLayers: _.get(dataSources, 'kibana.regionmap', []) - }; - +function createSourceInstance(sourceDescriptor) { const Source = ALL_SOURCES.find(Source => { return Source.type === sourceDescriptor.type; }); if (!Source) { throw new Error(`Unrecognized sourceType ${sourceDescriptor.type}`); } - return new Source(sourceDescriptor, dataMeta); + return new Source(sourceDescriptor); } @@ -66,8 +59,6 @@ function createStyleInstance(styleDescriptor) { } } -export const getMapState = ({ map }) => map && map.mapState; - export const getMapReady = ({ map }) => map && map.ready; export const getGoto = ({ map }) => map && map.goto; @@ -137,14 +128,12 @@ export const getDataFilters = createSelector( } ); -export const getDataSources = createSelector(getMetadata, metadata => metadata ? metadata.data_sources : null); export const getLayerList = createSelector( getLayerListRaw, - getDataSources, - (layerDescriptorList, dataSources) => { + (layerDescriptorList) => { return layerDescriptorList.map(layerDescriptor => - createLayerInstance(layerDescriptor, dataSources)); + createLayerInstance(layerDescriptor)); }); export const getSelectedLayer = createSelector( diff --git a/x-pack/plugins/gis/public/shared/layers/sources/ems_file_source/create_source_editor.js b/x-pack/plugins/gis/public/shared/layers/sources/ems_file_source/create_source_editor.js new file mode 100644 index 0000000000000..53f253017536e --- /dev/null +++ b/x-pack/plugins/gis/public/shared/layers/sources/ems_file_source/create_source_editor.js @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + + +import React from 'react'; +import { + EuiSelect, + EuiFormRow, +} from '@elastic/eui'; + +import { getEmsVectorFilesMeta } from '../../../../meta'; + + +export class EMSFileCreateSourceEditor extends React.Component { + + + state = { + emsFileOptionsRaw: null + }; + + _loadFileOptions = async () => { + const options = await getEmsVectorFilesMeta(); + if (this._isMounted) { + this.setState({ + emsFileOptionsRaw: options + }); + } + } + + componentWillUnmount() { + this._isMounted = false; + } + + componentDidMount() { + this._isMounted = true; + this._loadFileOptions(); + } + + render() { + + if (!this.state.emsFileOptionsRaw) { + return null; + } + + const emsVectorOptions = this.state.emsFileOptionsRaw ? this.state.emsFileOptionsRaw.map((file) => ({ + value: file.id, + text: file.name + })) : []; + + + return ( + + + + ); + } +} diff --git a/x-pack/plugins/gis/public/shared/layers/sources/ems_file_source.js b/x-pack/plugins/gis/public/shared/layers/sources/ems_file_source/ems_file_source.js similarity index 59% rename from x-pack/plugins/gis/public/shared/layers/sources/ems_file_source.js rename to x-pack/plugins/gis/public/shared/layers/sources/ems_file_source/ems_file_source.js index 85dd5e6dc156b..b4f16817bf630 100644 --- a/x-pack/plugins/gis/public/shared/layers/sources/ems_file_source.js +++ b/x-pack/plugins/gis/public/shared/layers/sources/ems_file_source/ems_file_source.js @@ -4,15 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AbstractVectorSource } from './vector_source'; +import { AbstractVectorSource } from '../vector_source'; import React from 'react'; -import { - EuiSelect, - EuiFormRow, -} from '@elastic/eui'; - -import { GIS_API_PATH } from '../../../../common/constants'; -import { emsServiceSettings } from '../../../kibana_services'; +import { GIS_API_PATH } from '../../../../../common/constants'; +import { emsServiceSettings } from '../../../../kibana_services'; +import { getEmsVectorFilesMeta } from '../../../../meta'; +import { EMSFileCreateSourceEditor } from './create_source_editor'; export class EMSFileSource extends AbstractVectorSource { @@ -28,38 +25,23 @@ export class EMSFileSource extends AbstractVectorSource { }; } - static renderEditor({ dataSourcesMeta, onPreviewSource }) { - - const emsVectorOptionsRaw = (dataSourcesMeta) ? dataSourcesMeta.ems.file : []; - const emsVectorOptions = emsVectorOptionsRaw ? emsVectorOptionsRaw.map((file) => ({ - value: file.id, - text: file.name - })) : []; - + static renderEditor({ onPreviewSource }) { const onChange = ({ target }) => { const selectedId = target.options[target.selectedIndex].value; const emsFileSourceDescriptor = EMSFileSource.createDescriptor(selectedId); - const emsFileSource = new EMSFileSource(emsFileSourceDescriptor, emsVectorOptionsRaw); + const emsFileSource = new EMSFileSource(emsFileSourceDescriptor); onPreviewSource(emsFileSource); }; - return ( - - - - ); + return ; } - constructor(descriptor, { emsFileLayers }) { + constructor(descriptor) { super(descriptor); - this._emsFiles = emsFileLayers; } async getGeoJsonWithMeta() { - const fileSource = this._emsFiles.find((source => source.id === this._descriptor.id)); + const emsFiles = await getEmsVectorFilesMeta(); + const fileSource = emsFiles.find((source => source.id === this._descriptor.id)); const fetchUrl = `../${GIS_API_PATH}/data/ems?id=${encodeURIComponent(this._descriptor.id)}`; const featureCollection = await AbstractVectorSource.getGeoJson(fileSource, fetchUrl); return { @@ -77,24 +59,24 @@ export class EMSFileSource extends AbstractVectorSource { } async getDisplayName() { - const fileSource = this._emsFiles.find((source => source.id === this._descriptor.id)); + const emsFiles = await getEmsVectorFilesMeta(); + const fileSource = emsFiles.find((source => source.id === this._descriptor.id)); return fileSource.name; } async getAttributions() { - const fileSource = this._emsFiles.find((source => source.id === this._descriptor.id)); + const emsFiles = await getEmsVectorFilesMeta(); + const fileSource = emsFiles.find((source => source.id === this._descriptor.id)); return fileSource.attributions; } async getStringFields() { - //todo: use map/service-settings instead. - const fileSource = this._emsFiles.find((source => source.id === this._descriptor.id)); - + const emsFiles = await getEmsVectorFilesMeta(); + const fileSource = emsFiles.find((source => source.id === this._descriptor.id)); return fileSource.fields.map(f => { return { name: f.name, label: f.description }; }); - } canFormatFeatureProperties() { diff --git a/x-pack/plugins/canvas/public/components/file_upload/index.js b/x-pack/plugins/gis/public/shared/layers/sources/ems_file_source/index.js similarity index 82% rename from x-pack/plugins/canvas/public/components/file_upload/index.js rename to x-pack/plugins/gis/public/shared/layers/sources/ems_file_source/index.js index 68c0f6150521b..9d0e503eb08ba 100644 --- a/x-pack/plugins/canvas/public/components/file_upload/index.js +++ b/x-pack/plugins/gis/public/shared/layers/sources/ems_file_source/index.js @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { FileUpload } from './file_upload'; +export { EMSFileSource } from './ems_file_source'; diff --git a/x-pack/plugins/gis/public/shared/layers/sources/ems_tms_source/create_source_editor.js b/x-pack/plugins/gis/public/shared/layers/sources/ems_tms_source/create_source_editor.js new file mode 100644 index 0000000000000..443a8d5170835 --- /dev/null +++ b/x-pack/plugins/gis/public/shared/layers/sources/ems_tms_source/create_source_editor.js @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + + +import React from 'react'; +import { + EuiSelect, + EuiFormRow, +} from '@elastic/eui'; + +import { getEmsTMSServices } from '../../../../meta'; + + +export class EMSTMSCreateSourceEditor extends React.Component { + + + state = { + emsTmsOptionsRaw: null + }; + + _loadTmsOptions = async () => { + const options = await getEmsTMSServices(); + if (this._isMounted) { + this.setState({ + emsTmsOptionsRaw: options + }); + } + } + + componentWillUnmount() { + this._isMounted = false; + } + + componentDidMount() { + this._isMounted = true; + this._loadTmsOptions(); + } + + render() { + + if (!this.state.emsTmsOptionsRaw) { + return null; + } + + const emsTileOptions = this.state.emsTmsOptionsRaw.map((service) => ({ + value: service.id, + text: service.id //due to not having human readable names + })); + + + return ( + + + + ); + } +} diff --git a/x-pack/plugins/gis/public/shared/layers/sources/ems_tms_source/ems_tms_source.js b/x-pack/plugins/gis/public/shared/layers/sources/ems_tms_source/ems_tms_source.js index 129f00ee6a541..b4b0e529f214a 100644 --- a/x-pack/plugins/gis/public/shared/layers/sources/ems_tms_source/ems_tms_source.js +++ b/x-pack/plugins/gis/public/shared/layers/sources/ems_tms_source/ems_tms_source.js @@ -6,11 +6,9 @@ import React from 'react'; import { AbstractTMSSource } from '../tms_source'; import { TileLayer } from '../../tile_layer'; -import { - EuiSelect, - EuiFormRow, -} from '@elastic/eui'; -import _ from 'lodash'; + +import { getEmsTMSServices } from '../../../../meta'; +import { EMSTMSCreateSourceEditor } from './create_source_editor'; export class EMSTMSSource extends AbstractTMSSource { @@ -27,34 +25,20 @@ export class EMSTMSSource extends AbstractTMSSource { }; } - static renderEditor({ dataSourcesMeta, onPreviewSource }) { - - const emsTmsOptionsRaw = _.get(dataSourcesMeta, "ems.tms", []); - const emsTileOptions = emsTmsOptionsRaw.map((service) => ({ - value: service.id, - text: service.id //due to not having human readable names - })); + static renderEditor({ onPreviewSource }) { const onChange = ({ target }) => { const selectedId = target.options[target.selectedIndex].value; const emsTMSSourceDescriptor = EMSTMSSource.createDescriptor(selectedId); - const emsTMSSource = new EMSTMSSource(emsTMSSourceDescriptor, emsTmsOptionsRaw); + const emsTMSSource = new EMSTMSSource(emsTMSSourceDescriptor); onPreviewSource(emsTMSSource); }; - return ( - - - - ); + + return ; } - constructor(descriptor, { emsTmsServices }) { + constructor(descriptor) { super(descriptor); - this._emsTileServices = emsTmsServices; } async getImmutableProperties() { @@ -64,12 +48,13 @@ export class EMSTMSSource extends AbstractTMSSource { ]; } - _getTMSOptions() { - if(!this._emsTileServices) { + async _getTMSOptions() { + const emsTileServices = await getEmsTMSServices(); + if(!emsTileServices) { return; } - return this._emsTileServices.find(service => { + return emsTileServices.find(service => { return service.id === this._descriptor.id; }); } @@ -93,7 +78,7 @@ export class EMSTMSSource extends AbstractTMSSource { } async getAttributions() { - const service = this._getTMSOptions(); + const service = await this._getTMSOptions(); if (!service || !service.attributionMarkdown) { return []; } @@ -110,10 +95,10 @@ export class EMSTMSSource extends AbstractTMSSource { }); } - getUrlTemplate() { - const service = this._getTMSOptions(); + async getUrlTemplate() { + const service = await this._getTMSOptions(); if (!service || !service.url) { - throw new Error('Can not generate EMS TMS url template'); + throw new Error('Cannot generate EMS TMS url template'); } return service.url; } diff --git a/x-pack/plugins/gis/public/shared/layers/sources/ems_tms_source/ems_tms_source.test.js b/x-pack/plugins/gis/public/shared/layers/sources/ems_tms_source/ems_tms_source.test.js index 1d5d54be06a98..1f29dbb7d1e6f 100644 --- a/x-pack/plugins/gis/public/shared/layers/sources/ems_tms_source/ems_tms_source.test.js +++ b/x-pack/plugins/gis/public/shared/layers/sources/ems_tms_source/ems_tms_source.test.js @@ -4,6 +4,21 @@ * you may not use this file except in compliance with the Elastic License. */ +jest.mock('../../../../meta', () => { + return { + getEmsTMSServices: async () => { + return [ + { + id: 'road_map', + attributionMarkdown: '[foobar](http://foobar.org) | [foobaz](http://foobaz.org)' + }, { + id: 'satellite', + attributionMarkdown: '[satellite](http://satellite.org)' + } + ]; + } + }; +}); import { EMSTMSSource, @@ -15,21 +30,10 @@ describe('EMSTMSSource', () => { const emsTmsSource = new EMSTMSSource({ id: 'road_map' - }, { - emsTmsServices: [ - { - id: 'road_map', - attributionMarkdown: '[foobar](http://foobar.org) | [foobaz](http://foobaz.org)' - }, { - id: 'satellite', - attributionMarkdown: '[satellite](http://satellite.org)' - } - ] }); const attributions = await emsTmsSource.getAttributions(); - expect(attributions).toEqual([ { label: 'foobar', diff --git a/x-pack/plugins/gis/public/shared/layers/sources/kibana_regionmap_source/create_source_editor.js b/x-pack/plugins/gis/public/shared/layers/sources/kibana_regionmap_source/create_source_editor.js index 3c8aceb8e8f3b..c740df0129ad6 100644 --- a/x-pack/plugins/gis/public/shared/layers/sources/kibana_regionmap_source/create_source_editor.js +++ b/x-pack/plugins/gis/public/shared/layers/sources/kibana_regionmap_source/create_source_editor.js @@ -10,48 +10,70 @@ import { EuiSelect, EuiFormRow, } from '@elastic/eui'; +import { getKibanaRegionList } from '../../../../meta'; const NO_REGIONMAP_LAYERS_MSG = 'No vector layers are available.' + ' Ask your system administrator to set "map.regionmap" in kibana.yml.'; -export function CreateSourceEditor({ onSelect, regionmapLayers }) { +export class CreateSourceEditor extends React.Component { - const regionmapOptions = regionmapLayers.map(({ name, url }) => { - return { - value: url, - text: name - }; - }); + state = { + regionmapLayers: [] + } - const onChange = ({ target }) => { - const selectedName = target.options[target.selectedIndex].text; - onSelect({ name: selectedName }); + _loadList = async () => { + const list = await getKibanaRegionList(); + if (this._isMounted) { + this.setState({ + regionmapLayers: list + }); + } }; - return ( - - - - ); + componentWillUnmount() { + this._isMounted = false; + } + + componentDidMount() { + this._isMounted = true; + this._loadList(); + } + + render() { + + + + const onChange = ({ target }) => { + const selectedName = target.options[target.selectedIndex].text; + this.props.onSelect({ name: selectedName }); + }; + + const regionmapOptions = this.state.regionmapLayers.map(({ name, url }) => { + return { + value: url, + text: name + }; + }); + + return ( + + + + ); + } } CreateSourceEditor.propTypes = { - onSelect: PropTypes.func.isRequired, - regionmapLayers: PropTypes.arrayOf(PropTypes.shape({ - url: PropTypes.string.isRequired, - name: PropTypes.string.isRequired, - })), + onSelect: PropTypes.func.isRequired }; -CreateSourceEditor.defaultProps = { - regionmapLayers: [], -}; + diff --git a/x-pack/plugins/gis/public/shared/layers/sources/kibana_regionmap_source/kibana_regionmap_source.js b/x-pack/plugins/gis/public/shared/layers/sources/kibana_regionmap_source/kibana_regionmap_source.js index cbdc31c516a37..9e02ec3bfb190 100644 --- a/x-pack/plugins/gis/public/shared/layers/sources/kibana_regionmap_source/kibana_regionmap_source.js +++ b/x-pack/plugins/gis/public/shared/layers/sources/kibana_regionmap_source/kibana_regionmap_source.js @@ -4,11 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import _ from 'lodash'; import { AbstractVectorSource } from '../vector_source'; import React from 'react'; import { CreateSourceEditor } from './create_source_editor'; +import { getKibanaRegionList } from '../../../../meta'; + export class KibanaRegionmapSource extends AbstractVectorSource { static type = 'REGIONMAP_FILE'; @@ -16,9 +17,8 @@ export class KibanaRegionmapSource extends AbstractVectorSource { static description = 'Vector shapes from static files configured in kibana.yml'; static icon = 'logoKibana'; - constructor(descriptor, { ymlFileLayers }) { + constructor(descriptor) { super(descriptor); - this._regionList = ymlFileLayers; } static createDescriptor(options) { @@ -28,19 +28,16 @@ export class KibanaRegionmapSource extends AbstractVectorSource { }; } - static renderEditor = ({ dataSourcesMeta, onPreviewSource }) => { - const regionmapLayers = _.get(dataSourcesMeta, 'kibana.regionmap', []); - + static renderEditor = ({ onPreviewSource }) => { const onSelect = (layerConfig) => { const sourceDescriptor = KibanaRegionmapSource.createDescriptor(layerConfig); - const source = new KibanaRegionmapSource(sourceDescriptor, { ymlFileLayers: regionmapLayers }); + const source = new KibanaRegionmapSource(sourceDescriptor); onPreviewSource(source); }; return ( ); }; @@ -53,7 +50,8 @@ export class KibanaRegionmapSource extends AbstractVectorSource { } async getGeoJsonWithMeta() { - const fileSource = this._regionList.find(source => source.name === this._descriptor.name); + const regionList = await getKibanaRegionList(); + const fileSource = regionList.find(source => source.name === this._descriptor.name); const featureCollection = await AbstractVectorSource.getGeoJson(fileSource, fileSource.url); return { data: featureCollection @@ -61,8 +59,8 @@ export class KibanaRegionmapSource extends AbstractVectorSource { } async getStringFields() { - //todo: use map/service-settings instead. - const fileSource = this._regionList.find((source => source.name === this._descriptor.name)); + const regionList = await getKibanaRegionList(); + const fileSource = regionList.find((source => source.name === this._descriptor.name)); return fileSource.fields.map(f => { return { name: f.name, label: f.description }; diff --git a/x-pack/plugins/gis/public/shared/layers/sources/kibana_tilemap_source/create_source_editor.js b/x-pack/plugins/gis/public/shared/layers/sources/kibana_tilemap_source/create_source_editor.js index 777767858fa0a..b20ccc1314f24 100644 --- a/x-pack/plugins/gis/public/shared/layers/sources/kibana_tilemap_source/create_source_editor.js +++ b/x-pack/plugins/gis/public/shared/layers/sources/kibana_tilemap_source/create_source_editor.js @@ -11,27 +11,54 @@ import { EuiFormRow, } from '@elastic/eui'; +import { getKibanaTileMap } from '../../../../meta'; + const NO_TILEMAP_LAYER_MSG = 'No tilemap layer is available.' + ' Ask your system administrator to set "map.tilemap.url" in kibana.yml.'; + + export class CreateSourceEditor extends Component { - componentDidMount() { - if (this.props.url) { + + state = { + url: null + } + + _loadUrl = async () => { + const tilemap = await getKibanaTileMap(); + if (this._isMounted) { + this.setState({ + url: tilemap.url + }); this.props.previewTilemap(this.props.url); } } + componentWillUnmount() { + this._isMounted = false; + } + + componentDidMount() { + this._isMounted = true; + this._loadUrl(); + } + render() { + + if (this.state.url === null) { + return null; + } + return ( ); @@ -39,6 +66,5 @@ export class CreateSourceEditor extends Component { } CreateSourceEditor.propTypes = { - previewTilemap: PropTypes.func.isRequired, - url: PropTypes.string, + previewTilemap: PropTypes.func.isRequired }; diff --git a/x-pack/plugins/gis/public/shared/layers/sources/kibana_tilemap_source/kibana_tilemap_source.js b/x-pack/plugins/gis/public/shared/layers/sources/kibana_tilemap_source/kibana_tilemap_source.js index a14b8d90fc976..a847aa32535c2 100644 --- a/x-pack/plugins/gis/public/shared/layers/sources/kibana_tilemap_source/kibana_tilemap_source.js +++ b/x-pack/plugins/gis/public/shared/layers/sources/kibana_tilemap_source/kibana_tilemap_source.js @@ -7,7 +7,6 @@ import React from 'react'; import { AbstractTMSSource } from '../tms_source'; import { TileLayer } from '../../tile_layer'; import { CreateSourceEditor } from './create_source_editor'; - export class KibanaTilemapSource extends AbstractTMSSource { static type = 'KIBANA_TILEMAP'; @@ -22,20 +21,19 @@ export class KibanaTilemapSource extends AbstractTMSSource { }; } - static renderEditor = ({ dataSourcesMeta, onPreviewSource }) => { - const { url } = dataSourcesMeta ? dataSourcesMeta.kibana.tilemap : {}; + static renderEditor = ({ onPreviewSource }) => { const previewTilemap = (urlTemplate) => { const sourceDescriptor = KibanaTilemapSource.createDescriptor(urlTemplate); const source = new KibanaTilemapSource(sourceDescriptor); onPreviewSource(source); }; - return (); + return (); }; async getImmutableProperties() { return [ { label: 'Data source', value: KibanaTilemapSource.title }, - { label: 'Tilemap url', value: this.getUrlTemplate() }, + { label: 'Tilemap url', value: (await this.getUrlTemplate()) }, ]; } @@ -54,11 +52,11 @@ export class KibanaTilemapSource extends AbstractTMSSource { } - getUrlTemplate() { + async getUrlTemplate() { return this._descriptor.url; } async getDisplayName() { - return this.getUrlTemplate(); + return await this.getUrlTemplate(); } } diff --git a/x-pack/plugins/gis/public/shared/layers/sources/tms_source.js b/x-pack/plugins/gis/public/shared/layers/sources/tms_source.js index 852dffd1ef5f2..53a920a1393c1 100644 --- a/x-pack/plugins/gis/public/shared/layers/sources/tms_source.js +++ b/x-pack/plugins/gis/public/shared/layers/sources/tms_source.js @@ -8,7 +8,7 @@ import { AbstractSource } from './source'; export class AbstractTMSSource extends AbstractSource { - getUrlTemplate() { + async getUrlTemplate() { throw new Error('Should implement TMSSource#getUrlTemplate'); } } diff --git a/x-pack/plugins/gis/public/shared/layers/tile_layer.js b/x-pack/plugins/gis/public/shared/layers/tile_layer.js index 75e42d8123025..99fd312c6fd86 100644 --- a/x-pack/plugins/gis/public/shared/layers/tile_layer.js +++ b/x-pack/plugins/gis/public/shared/layers/tile_layer.js @@ -64,15 +64,38 @@ export class TileLayer extends AbstractLayer { }); } + async syncData({ startLoading, stopLoading, onLoadError, dataFilters }) { + if (!this.isVisible() || !this.showAtZoomLevel(dataFilters.zoom)) { + return; + } + const sourceDataId = 'source'; + const requestToken = Symbol(`layer-source-refresh:${ this.getId()} - source`); + startLoading(sourceDataId, requestToken, dataFilters); + try { + const url = await this._source.getUrlTemplate(); + stopLoading(sourceDataId, requestToken, url, {}); + } catch(error) { + onLoadError(sourceDataId, requestToken, error.message); + } + } + async syncLayerWithMB(mbMap) { + const source = mbMap.getSource(this.getId()); - const layerId = this.getId() + '_raster'; + const mbLayerId = this.getId() + '_raster'; if (source) { + // If source exists, just sync style + this._setTileLayerProperties(mbMap, mbLayerId); + return; + } + + const sourceDataRquest = this.getSourceDataRequest(); + const url = sourceDataRquest.getData(); + if (!url) { return; } - const url = this._source.getUrlTemplate(); const sourceId = this.getId(); mbMap.addSource(sourceId, { type: 'raster', @@ -82,7 +105,7 @@ export class TileLayer extends AbstractLayer { }); mbMap.addLayer({ - id: layerId, + id: mbLayerId, type: 'raster', source: sourceId, minzoom: 0, @@ -90,13 +113,16 @@ export class TileLayer extends AbstractLayer { }); await this._tileLoadErrorTracker(mbMap, url); + this._setTileLayerProperties(mbMap, mbLayerId); + } - mbMap.setLayoutProperty(layerId, 'visibility', this.isVisible() ? 'visible' : 'none'); - mbMap.setLayerZoomRange(layerId, this._descriptor.minZoom, this._descriptor.maxZoom); + _setTileLayerProperties(mbMap, mbLayerId) { + mbMap.setLayoutProperty(mbLayerId, 'visibility', this.isVisible() ? 'visible' : 'none'); + mbMap.setLayerZoomRange(mbLayerId, this._descriptor.minZoom, this._descriptor.maxZoom); this._style && this._style.setMBPaintProperties({ alpha: this.getAlpha(), mbMap, - layerId, + mbLayerId, }); } diff --git a/x-pack/plugins/gis/public/store/config.js b/x-pack/plugins/gis/public/store/config.js deleted file mode 100644 index 09eb91cdcac8e..0000000000000 --- a/x-pack/plugins/gis/public/store/config.js +++ /dev/null @@ -1,22 +0,0 @@ -/* - * 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 { SET_META } from '../actions/store_actions'; - -const INITIAL_STATE = { - meta: null -}; - -// Reducer -function config(state = INITIAL_STATE, action) { - switch (action.type) { - case SET_META: - return { ...state, meta: action.meta }; - default: - return state; - } -} - -export default config; diff --git a/x-pack/plugins/gis/public/store/map.js b/x-pack/plugins/gis/public/store/map.js index 3aa6146486b39..9b5c48a20fe9b 100644 --- a/x-pack/plugins/gis/public/store/map.js +++ b/x-pack/plugins/gis/public/store/map.js @@ -20,7 +20,6 @@ import { MAP_EXTENT_CHANGED, MAP_READY, MAP_DESTROYED, - SET_TIME_FILTERS, SET_QUERY, UPDATE_LAYER_PROP, UPDATE_LAYER_STYLE_FOR_SELECTED_LAYER, @@ -163,12 +162,16 @@ export function map(state = INITIAL_STATE, action) { buffer: action.mapState.buffer, }; return { ...state, mapState: { ...state.mapState, ...newMapState } }; - case SET_TIME_FILTERS: - const { from, to } = action; - return { ...state, mapState: { ...state.mapState, timeFilters: { from, to } } }; case SET_QUERY: - const { query } = action; - return { ...state, mapState: { ...state.mapState, query } }; + const { query, timeFilters } = action; + return { + ...state, + mapState: { + ...state.mapState, + query, + timeFilters, + } + }; case SET_REFRESH_CONFIG: const { isPaused, interval } = action; return { diff --git a/x-pack/plugins/gis/public/store/store.js b/x-pack/plugins/gis/public/store/store.js index 2bad2f6cb7063..22b964ee301cf 100644 --- a/x-pack/plugins/gis/public/store/store.js +++ b/x-pack/plugins/gis/public/store/store.js @@ -8,16 +8,13 @@ import { combineReducers, applyMiddleware, createStore, compose } from 'redux'; import thunk from 'redux-thunk'; import ui from './ui'; import { map } from './map'; -import { loadMetaResources } from "../actions/store_actions"; -import config from './config'; // TODO this should not be exported and all access to the store be via getStore export let store; const rootReducer = combineReducers({ map, - ui, - config + ui }); const enhancers = [ applyMiddleware(thunk) ]; @@ -36,6 +33,5 @@ export const getStore = async function () { storeConfig, composeEnhancers(...enhancers) ); - await loadMetaResources(store.dispatch); return store; }; diff --git a/x-pack/plugins/graph/index.js b/x-pack/plugins/graph/index.js index 83134f20d4f82..39dbc148772ec 100644 --- a/x-pack/plugins/graph/index.js +++ b/x-pack/plugins/graph/index.js @@ -7,6 +7,7 @@ import { resolve } from 'path'; import Boom from 'boom'; +import migrations from './migrations'; import { initServer } from './server'; import mappings from './mappings.json'; @@ -28,7 +29,8 @@ export function graph(kibana) { styleSheetPaths: resolve(__dirname, 'public/index.scss'), hacks: ['plugins/graph/hacks/toggle_app_link_in_nav'], home: ['plugins/graph/register_feature'], - mappings + mappings, + migrations, }, config(Joi) { diff --git a/x-pack/plugins/graph/migrations.js b/x-pack/plugins/graph/migrations.js new file mode 100644 index 0000000000000..d454de49f2b00 --- /dev/null +++ b/x-pack/plugins/graph/migrations.js @@ -0,0 +1,41 @@ +/* + * 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 { get } from 'lodash'; + +export default { + 'graph-workspace': { + '7.0.0': (doc) => { + // Set new "references" attribute + doc.references = doc.references || []; + // Migrate index pattern + const wsState = get(doc, 'attributes.wsState'); + if (typeof wsState !== 'string') { + return doc; + } + let state; + try { + state = JSON.parse(JSON.parse(wsState)); + } catch (e) { + // Let it go, the data is invalid and we'll leave it as is + return doc; + } + const { indexPattern } = state; + if (!indexPattern) { + return doc; + } + state.indexPatternRefName = 'indexPattern_0'; + delete state.indexPattern; + doc.attributes.wsState = JSON.stringify(JSON.stringify(state)); + doc.references.push({ + name: 'indexPattern_0', + type: 'index-pattern', + id: indexPattern, + }); + return doc; + } + } +}; diff --git a/x-pack/plugins/graph/migrations.test.js b/x-pack/plugins/graph/migrations.test.js new file mode 100644 index 0000000000000..93162d94857ce --- /dev/null +++ b/x-pack/plugins/graph/migrations.test.js @@ -0,0 +1,102 @@ +/* + * 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 migrations from './migrations'; + +describe('graph-workspace', () => { + describe('7.0.0', () => { + const migration = migrations['graph-workspace']['7.0.0']; + + test('returns doc on empty object', () => { + expect(migration({})).toMatchInlineSnapshot(` +Object { + "references": Array [], +} +`); + }); + + test('returns doc when wsState is not a string', () => { + const doc = { + id: '1', + attributes: { + wsState: true, + }, + }; + expect(migration(doc)).toMatchInlineSnapshot(` +Object { + "attributes": Object { + "wsState": true, + }, + "id": "1", + "references": Array [], +} +`); + }); + + test('returns doc when wsState is not valid JSON', () => { + const doc = { + id: '1', + attributes: { + wsState: '123abc', + }, + }; + expect(migration(doc)).toMatchInlineSnapshot(` +Object { + "attributes": Object { + "wsState": "123abc", + }, + "id": "1", + "references": Array [], +} +`); + }); + + test('returns doc when "indexPattern" is missing from wsState', () => { + const doc = { + id: '1', + attributes: { + wsState: JSON.stringify(JSON.stringify({ foo: true })), + }, + }; + expect(migration(doc)).toMatchInlineSnapshot(` +Object { + "attributes": Object { + "wsState": "\\"{\\\\\\"foo\\\\\\":true}\\"", + }, + "id": "1", + "references": Array [], +} +`); + }); + + test('extract "indexPattern" attribute from doc', () => { + const doc = { + id: '1', + attributes: { + wsState: JSON.stringify(JSON.stringify({ foo: true, indexPattern: 'pattern*' })), + bar: true, + }, + }; + const migratedDoc = migration(doc); + expect(migratedDoc).toMatchInlineSnapshot(` +Object { + "attributes": Object { + "bar": true, + "wsState": "\\"{\\\\\\"foo\\\\\\":true,\\\\\\"indexPatternRefName\\\\\\":\\\\\\"indexPattern_0\\\\\\"}\\"", + }, + "id": "1", + "references": Array [ + Object { + "id": "pattern*", + "name": "indexPattern_0", + "type": "index-pattern", + }, + ], +} +`); + }); + }); +}); diff --git a/x-pack/plugins/graph/public/services/saved_workspace.js b/x-pack/plugins/graph/public/services/saved_workspace.js index eb0c0e1dd936a..f25743900dab8 100644 --- a/x-pack/plugins/graph/public/services/saved_workspace.js +++ b/x-pack/plugins/graph/public/services/saved_workspace.js @@ -7,6 +7,10 @@ import { uiModules } from 'ui/modules'; import { SavedObjectProvider } from 'ui/courier'; import { i18n } from '@kbn/i18n'; +import { + extractReferences, + injectReferences, +} from './saved_workspace_references'; const module = uiModules.get('app/dashboard'); @@ -21,6 +25,8 @@ export function SavedWorkspaceProvider(Private) { type: SavedWorkspace.type, mapping: SavedWorkspace.mapping, searchSource: SavedWorkspace.searchsource, + extractReferences: extractReferences, + injectReferences: injectReferences, // if this is null/undefined then the SavedObject will be assigned the defaults id: id, diff --git a/x-pack/plugins/graph/public/services/saved_workspace_references.js b/x-pack/plugins/graph/public/services/saved_workspace_references.js new file mode 100644 index 0000000000000..a1b4254685c40 --- /dev/null +++ b/x-pack/plugins/graph/public/services/saved_workspace_references.js @@ -0,0 +1,53 @@ +/* + * 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 function extractReferences({ attributes, references = [] }) { + // For some reason, wsState comes in stringified 2x + const state = JSON.parse(JSON.parse(attributes.wsState)); + const { indexPattern } = state; + if (!indexPattern) { + throw new Error('indexPattern attribute is missing in "wsState"'); + } + state.indexPatternRefName = 'indexPattern_0'; + delete state.indexPattern; + return { + references: [ + ...references, + { + name: 'indexPattern_0', + type: 'index-pattern', + id: indexPattern, + } + ], + attributes: { + ...attributes, + wsState: JSON.stringify(JSON.stringify(state)), + }, + }; +} + +export function injectReferences(savedObject, references) { + // Skip if wsState is missing, at the time of development of this, there is no guarantee each + // saved object has wsState. + if (typeof savedObject.wsState !== 'string') { + return; + } + // Only need to parse / stringify once here compared to extractReferences + const state = JSON.parse(savedObject.wsState); + // Like the migration, skip injectReferences if "indexPatternRefName" is missing + if (!state.indexPatternRefName) { + return; + } + const indexPatternReference = references.find(reference => reference.name === state.indexPatternRefName); + if (!indexPatternReference) { + // Throw an error as "indexPatternRefName" means the reference exists within + // "references" and in this scenario we have bad data. + throw new Error(`Could not find reference "${state.indexPatternRefName}"`); + } + state.indexPattern = indexPatternReference.id; + delete state.indexPatternRefName; + savedObject.wsState = JSON.stringify(state); +} diff --git a/x-pack/plugins/graph/public/services/saved_workspace_references.test.js b/x-pack/plugins/graph/public/services/saved_workspace_references.test.js new file mode 100644 index 0000000000000..01eb7f9ead1f0 --- /dev/null +++ b/x-pack/plugins/graph/public/services/saved_workspace_references.test.js @@ -0,0 +1,124 @@ +/* + * 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 { extractReferences, injectReferences } from './saved_workspace_references'; + +describe('extractReferences', () => { + test('extracts references from wsState', () => { + const doc = { + id: '1', + attributes: { + foo: true, + wsState: JSON.stringify( + JSON.stringify({ + indexPattern: 'pattern*', + bar: true, + }) + ), + }, + }; + const updatedDoc = extractReferences(doc); + expect(updatedDoc).toMatchInlineSnapshot(` +Object { + "attributes": Object { + "foo": true, + "wsState": "\\"{\\\\\\"bar\\\\\\":true,\\\\\\"indexPatternRefName\\\\\\":\\\\\\"indexPattern_0\\\\\\"}\\"", + }, + "references": Array [ + Object { + "id": "pattern*", + "name": "indexPattern_0", + "type": "index-pattern", + }, + ], +} +`); + }); + + test('fails when indexPattern is missing from workspace', () => { + const doc = { + id: '1', + attributes: { + wsState: JSON.stringify( + JSON.stringify({ + bar: true, + }) + ), + }, + }; + expect(() => extractReferences(doc)).toThrowErrorMatchingInlineSnapshot( + `"indexPattern attribute is missing in \\"wsState\\""` + ); + }); +}); + +describe('injectReferences', () => { + test('injects references into context', () => { + const context = { + id: '1', + foo: true, + wsState: JSON.stringify({ + indexPatternRefName: 'indexPattern_0', + bar: true, + }), + }; + const references = [ + { + name: 'indexPattern_0', + type: 'index-pattern', + id: 'pattern*', + }, + ]; + injectReferences(context, references); + expect(context).toMatchInlineSnapshot(` +Object { + "foo": true, + "id": "1", + "wsState": "{\\"bar\\":true,\\"indexPattern\\":\\"pattern*\\"}", +} +`); + }); + + test('skips when wsState is not a string', () => { + const context = { + id: '1', + foo: true, + }; + injectReferences(context, []); + expect(context).toMatchInlineSnapshot(` +Object { + "foo": true, + "id": "1", +} +`); + }); + + test('skips when indexPatternRefName is missing wsState', () => { + const context = { + id: '1', + wsState: JSON.stringify({ bar: true }), + }; + injectReferences(context, []); + expect(context).toMatchInlineSnapshot(` +Object { + "id": "1", + "wsState": "{\\"bar\\":true}", +} +`); + }); + + test(`fails when it can't find the reference in the array`, () => { + const context = { + id: '1', + wsState: JSON.stringify({ + indexPatternRefName: 'indexPattern_0', + }), + }; + expect(() => injectReferences(context, [])).toThrowErrorMatchingInlineSnapshot( + `"Could not find reference \\"indexPattern_0\\""` + ); + }); +}); diff --git a/x-pack/plugins/index_management/public/index_management_extensions.js b/x-pack/plugins/index_management/public/index_management_extensions.js index 32d07e89c1fdf..28c5ad974b538 100644 --- a/x-pack/plugins/index_management/public/index_management_extensions.js +++ b/x-pack/plugins/index_management/public/index_management_extensions.js @@ -47,6 +47,7 @@ const badgeExtensions = [ label: i18n.translate('xpack.idxMgmt.frozenBadgeLabel', { defaultMessage: 'Frozen', }), + filterExpression: 'isFrozen:true', color: 'primary' } ]; diff --git a/x-pack/plugins/index_management/public/lib/render_badges.js b/x-pack/plugins/index_management/public/lib/render_badges.js index 4f8c224c084c5..661dad876a7f0 100644 --- a/x-pack/plugins/index_management/public/lib/render_badges.js +++ b/x-pack/plugins/index_management/public/lib/render_badges.js @@ -6,15 +6,34 @@ import React, { Fragment } from 'react'; -import { EuiBadge } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { EuiBadge, EuiSearchBar } from '@elastic/eui'; import { getBadgeExtensions } from '../index_management_extensions'; -export const renderBadges = (index) => { +export const renderBadges = (index, filterChanged) => { const badgeLabels = []; - getBadgeExtensions().forEach(({ matchIndex, label, color }) => { + getBadgeExtensions().forEach(({ matchIndex, label, color, filterExpression }) => { if (matchIndex(index)) { + const clickHandler = () => { + filterChanged + && filterExpression + && filterChanged(EuiSearchBar.Query.parse(filterExpression)); + }; badgeLabels.push( - {' '}{label} + {' '} + + {label} + ); } diff --git a/x-pack/plugins/index_management/public/sections/index_list/components/index_table/index_table.js b/x-pack/plugins/index_management/public/sections/index_list/components/index_table/index_table.js index 6ff588a6a4fe0..944d8fcec87c0 100644 --- a/x-pack/plugins/index_management/public/sections/index_list/components/index_table/index_table.js +++ b/x-pack/plugins/index_management/public/sections/index_list/components/index_table/index_table.js @@ -213,20 +213,23 @@ export class IndexTableUi extends Component { } buildRowCell(fieldName, value, index) { - const { openDetailPanel } = this.props; + const { openDetailPanel, filterChanged } = this.props; if (fieldName === 'health') { return {value}; } else if (fieldName === 'name') { return ( - { - openDetailPanel(value); - }} - > - {value}{renderBadges(index)} - + + { + openDetailPanel(value); + }} + > + {value} + + {renderBadges(index, filterChanged)} + ); } return value; diff --git a/x-pack/plugins/infra/public/components/logging/log_flyout.tsx b/x-pack/plugins/infra/public/components/logging/log_flyout.tsx new file mode 100644 index 0000000000000..7c277dd78ba78 --- /dev/null +++ b/x-pack/plugins/infra/public/components/logging/log_flyout.tsx @@ -0,0 +1,114 @@ +/* + * 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 { + EuiBasicTable, + EuiButtonIcon, + EuiFlyout, + EuiFlyoutBody, + EuiFlyoutHeader, + EuiTitle, + EuiToolTip, +} from '@elastic/eui'; +import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react'; +import React from 'react'; +import styled from 'styled-components'; +import { InfraLogItem, InfraLogItemField } from '../../graphql/types'; +import { InfraLoadingPanel } from '../loading'; +interface Props { + flyoutItem: InfraLogItem | null; + hideFlyout: () => void; + setFilter: (filter: string) => void; + intl: InjectedIntl; + loading: boolean; +} + +export const LogFlyout = injectI18n( + ({ flyoutItem, loading, hideFlyout, setFilter, intl }: Props) => { + const handleFilter = (field: InfraLogItemField) => () => { + const filter = `${field.field}:"${field.value}"`; + setFilter(filter); + }; + + const columns = [ + { + field: 'field', + name: intl.formatMessage({ + defaultMessage: 'Field', + id: 'xpack.infra.logFlyout.fieldColumnLabel', + }), + sortable: true, + }, + { + field: 'value', + name: intl.formatMessage({ + defaultMessage: 'Value', + id: 'xpack.infra.logFlyout.valueColumnLabel', + }), + sortable: true, + render: (name: string, item: InfraLogItemField) => ( + + + + + {item.value} + + ), + }, + ]; + return ( + hideFlyout()} size="m"> + + +

+ +

+
+
+ + {loading || flyoutItem === null ? ( + + + + ) : ( + + )} + +
+ ); + } +); + +export const InfraFlyoutLoadingPanel = styled.div` + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; +`; diff --git a/x-pack/plugins/infra/public/components/logging/log_text_stream/item_date_field.tsx b/x-pack/plugins/infra/public/components/logging/log_text_stream/item_date_field.tsx index f75056e23aef0..8550033e471c6 100644 --- a/x-pack/plugins/infra/public/components/logging/log_text_stream/item_date_field.tsx +++ b/x-pack/plugins/infra/public/components/logging/log_text_stream/item_date_field.tsx @@ -64,6 +64,7 @@ const LogTextStreamItemDateFieldWrapper = LogTextStreamItemField.extend.attrs<{ border-right: solid 2px ${props => props.theme.eui.euiColorLightShade}; color: ${props => props.theme.eui.euiColorDarkShade}; white-space: pre; + padding: 0 ${props => props.theme.eui.paddingSizes.l}; ${props => (props.hasHighlights ? highlightedFieldStyle : '')}; ${props => (props.isHovered ? hoveredFieldStyle : '')}; diff --git a/x-pack/plugins/infra/public/components/logging/log_text_stream/item_field.tsx b/x-pack/plugins/infra/public/components/logging/log_text_stream/item_field.tsx index c9d360fc116a7..4b6dcdb635ad9 100644 --- a/x-pack/plugins/infra/public/components/logging/log_text_stream/item_field.tsx +++ b/x-pack/plugins/infra/public/components/logging/log_text_stream/item_field.tsx @@ -19,5 +19,5 @@ export const LogTextStreamItemField = styled.div.attrs<{ [switchProp.default]: props.theme.eui.euiFontSize, })}; line-height: ${props => props.theme.eui.euiLineHeight}; - padding: 2px ${props => props.theme.eui.euiSize}; + padding: 2px ${props => props.theme.eui.euiSize} 2px 0; `; diff --git a/x-pack/plugins/infra/public/components/logging/log_text_stream/item_message_field.tsx b/x-pack/plugins/infra/public/components/logging/log_text_stream/item_message_field.tsx index 77ef813261818..39f75d5b7ab64 100644 --- a/x-pack/plugins/infra/public/components/logging/log_text_stream/item_message_field.tsx +++ b/x-pack/plugins/infra/public/components/logging/log_text_stream/item_message_field.tsx @@ -105,7 +105,6 @@ const LogTextStreamItemMessageFieldWrapper = LogTextStreamItemField.extend.attrs const HighlightSpan = styled.span` display: inline-block; - padding: 0 ${props => props.theme.eui.euiSizeXs}; background-color: ${props => props.theme.eui.euiColorSecondary}; color: ${props => props.theme.eui.euiColorGhost}; font-weight: ${props => props.theme.eui.euiFontWeightMedium}; diff --git a/x-pack/plugins/infra/public/components/logging/log_text_stream/item_view.tsx b/x-pack/plugins/infra/public/components/logging/log_text_stream/item_view.tsx index c4a75771fa5a0..055b92250b0f8 100644 --- a/x-pack/plugins/infra/public/components/logging/log_text_stream/item_view.tsx +++ b/x-pack/plugins/infra/public/components/logging/log_text_stream/item_view.tsx @@ -14,10 +14,11 @@ interface StreamItemProps { item: StreamItem; scale: TextScale; wrap: boolean; + openFlyoutWithItem: (id: string) => void; } export const LogTextStreamItemView = React.forwardRef( - ({ item, scale, wrap }, ref) => { + ({ item, scale, wrap, openFlyoutWithItem }, ref) => { switch (item.kind) { case 'logEntry': return ( @@ -27,6 +28,7 @@ export const LogTextStreamItemView = React.forwardRef( searchResult={item.searchResult} scale={scale} wrap={wrap} + openFlyoutWithItem={openFlyoutWithItem} /> ); } diff --git a/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_item_view.tsx b/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_item_view.tsx index 2e910cad56785..fb8642c3bd680 100644 --- a/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_item_view.tsx +++ b/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_item_view.tsx @@ -4,9 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ +import { darken, transparentize } from 'polished'; import * as React from 'react'; import styled from 'styled-components'; +import { EuiButtonIcon, EuiToolTip } from '@elastic/eui'; +import { InjectedIntl, injectI18n } from '@kbn/i18n/react'; import { LogEntry } from '../../../../common/log_entry'; import { SearchResult } from '../../../../common/log_search_result'; import { TextScale } from '../../../../common/log_text_scale'; @@ -20,65 +23,110 @@ interface LogTextStreamLogEntryItemViewProps { searchResult?: SearchResult; scale: TextScale; wrap: boolean; + openFlyoutWithItem: (id: string) => void; + intl: InjectedIntl; } interface LogTextStreamLogEntryItemViewState { isHovered: boolean; } -export class LogTextStreamLogEntryItemView extends React.PureComponent< - LogTextStreamLogEntryItemViewProps, - LogTextStreamLogEntryItemViewState -> { - public readonly state = { - isHovered: false, - }; +export const LogTextStreamLogEntryItemView = injectI18n( + class extends React.PureComponent< + LogTextStreamLogEntryItemViewProps, + LogTextStreamLogEntryItemViewState + > { + public readonly state = { + isHovered: false, + }; - public handleMouseEnter: React.MouseEventHandler = () => { - this.setState({ - isHovered: true, - }); - }; + public handleMouseEnter: React.MouseEventHandler = () => { + this.setState({ + isHovered: true, + }); + }; - public handleMouseLeave: React.MouseEventHandler = () => { - this.setState({ - isHovered: false, - }); - }; + public handleMouseLeave: React.MouseEventHandler = () => { + this.setState({ + isHovered: false, + }); + }; - public render() { - const { boundingBoxRef, logEntry, scale, searchResult, wrap } = this.props; - const { isHovered } = this.state; + public handleClick: React.MouseEventHandler = () => { + this.props.openFlyoutWithItem(this.props.logEntry.gid); + }; - return ( - - - {formatTime(logEntry.fields.time)} - - - {logEntry.fields.message} - - - ); + + {formatTime(logEntry.fields.time)} + + + {isHovered ? ( + + + + ) : ( + + )} + + + {logEntry.fields.message} + + + ); + } } +); + +interface IconProps { + isHovered: boolean; } +const EmptyIcon = styled.div` + width: 24px; +`; + +const LogTextStreamIconDiv = styled('div')` + flex-grow: 0; + background-color: ${props => + props.isHovered + ? props.theme.darkMode + ? transparentize(0.9, darken(0.05, props.theme.eui.euiColorHighlight)) + : darken(0.05, props.theme.eui.euiColorHighlight) + : 'transparent'}; + text-align: center; + user-select: none; +`; + const LogTextStreamLogEntryItemDiv = styled.div` font-family: ${props => props.theme.eui.euiCodeFontFamily}; font-size: ${props => props.theme.eui.euiFontSize}; diff --git a/x-pack/plugins/infra/public/components/logging/log_text_stream/scrollable_log_text_stream_view.tsx b/x-pack/plugins/infra/public/components/logging/log_text_stream/scrollable_log_text_stream_view.tsx index eb74ffcc29394..47db7a6716de8 100644 --- a/x-pack/plugins/infra/public/components/logging/log_text_stream/scrollable_log_text_stream_view.tsx +++ b/x-pack/plugins/infra/public/components/logging/log_text_stream/scrollable_log_text_stream_view.tsx @@ -42,6 +42,8 @@ interface ScrollableLogTextStreamViewProps { } ) => any; loadNewerItems: () => void; + setFlyoutItem: (id: string) => void; + showFlyout: () => void; } interface ScrollableLogTextStreamViewState { @@ -141,7 +143,13 @@ export class ScrollableLogTextStreamView extends React.PureComponent< key={getStreamItemId(item)} > {measureRef => ( - + )} ))} @@ -160,6 +168,11 @@ export class ScrollableLogTextStreamView extends React.PureComponent< } } + private handleOpenFlyout = (id: string) => { + this.props.setFlyoutItem(id); + this.props.showFlyout(); + }; + private handleReload = () => { const { jumpToTarget, target } = this.props; diff --git a/x-pack/plugins/infra/public/components/waffle/index.tsx b/x-pack/plugins/infra/public/components/nodes_overview/index.tsx similarity index 56% rename from x-pack/plugins/infra/public/components/waffle/index.tsx rename to x-pack/plugins/infra/public/components/nodes_overview/index.tsx index d38301f6eb1f8..6663b2354a40a 100644 --- a/x-pack/plugins/infra/public/components/waffle/index.tsx +++ b/x-pack/plugins/infra/public/components/nodes_overview/index.tsx @@ -10,34 +10,29 @@ import React from 'react'; import styled from 'styled-components'; import { - isWaffleMapGroupWithGroups, - isWaffleMapGroupWithNodes, -} from '../../containers/waffle/type_guards'; -import { InfraMetricType, InfraNodeType, InfraTimerangeInput } from '../../graphql/types'; -import { - InfraFormatterType, - InfraWaffleData, - InfraWaffleMapBounds, - InfraWaffleMapGroup, - InfraWaffleMapOptions, -} from '../../lib/lib'; + InfraMetricType, + InfraNode, + InfraNodeType, + InfraTimerangeInput, +} from '../../graphql/types'; +import { InfraFormatterType, InfraWaffleMapBounds, InfraWaffleMapOptions } from '../../lib/lib'; import { KueryFilterQuery } from '../../store/local/waffle_filter'; import { createFormatter } from '../../utils/formatters'; -import { AutoSizer } from '../auto_sizer'; import { InfraLoadingPanel } from '../loading'; -import { GroupOfGroups } from './group_of_groups'; -import { GroupOfNodes } from './group_of_nodes'; -import { Legend } from './legend'; -import { applyWaffleMapLayout } from './lib/apply_wafflemap_layout'; +import { Map } from '../waffle/map'; +import { ViewSwitcher } from '../waffle/view_switcher'; +import { TableView } from './table'; interface Props { options: InfraWaffleMapOptions; nodeType: InfraNodeType; - map: InfraWaffleData; + nodes: InfraNode[]; loading: boolean; reload: () => void; onDrilldown: (filter: KueryFilterQuery) => void; timeRange: InfraTimerangeInput; + onViewChange: (view: string) => void; + view: string; intl: InjectedIntl; } @@ -71,24 +66,8 @@ const METRIC_FORMATTERS: MetricFormatters = { }, }; -const extractValuesFromMap = (groups: InfraWaffleMapGroup[], values: number[] = []): number[] => { - return groups.reduce((acc: number[], group: InfraWaffleMapGroup) => { - if (isWaffleMapGroupWithGroups(group)) { - return acc.concat(extractValuesFromMap(group.groups, values)); - } - if (isWaffleMapGroupWithNodes(group)) { - return acc.concat( - group.nodes.map(node => { - return node.metric.value || 0; - }) - ); - } - return acc; - }, values); -}; - -const calculateBoundsFromMap = (map: InfraWaffleData): InfraWaffleMapBounds => { - const values = extractValuesFromMap(map); +const calculateBoundsFromNodes = (nodes: InfraNode[]): InfraWaffleMapBounds => { + const values = nodes.map(node => node.metric.value); // if there is only one value then we need to set the bottom range to zero if (values.length === 1) { values.unshift(0); @@ -96,11 +75,11 @@ const calculateBoundsFromMap = (map: InfraWaffleData): InfraWaffleMapBounds => { return { min: min(values) || 0, max: max(values) || 0 }; }; -export const Waffle = injectI18n( +export const NodesOverview = injectI18n( class extends React.Component { public static displayName = 'Waffle'; public render() { - const { loading, map, reload, timeRange, intl } = this.props; + const { loading, nodes, nodeType, reload, intl, view, options, timeRange } = this.props; if (loading) { return ( ); - } else if (!loading && map && map.length === 0) { + } else if (!loading && nodes && nodes.length === 0) { return ( - {({ measureRef, content: { width = 0, height = 0 } }) => { - const groupsWithLayout = applyWaffleMapLayout(map, width, height); - return ( - measureRef(el)} - data-test-subj="waffleMap" - > - - {groupsWithLayout.map(this.renderGroup(bounds, timeRange))} - - - - ); - }} - + + + + + {view === 'table' ? ( + + + + ) : ( + + + + )} + ); } + private handleViewChange = (view: string) => this.props.onViewChange(view); + // TODO: Change this to a real implimentation using the tickFormatter from the prototype as an example. private formatter = (val: string | number) => { const { metric } = this.props.options; @@ -204,61 +194,31 @@ export const Waffle = injectI18n( }); return; }; - - private renderGroup = (bounds: InfraWaffleMapBounds, timeRange: InfraTimerangeInput) => ( - group: InfraWaffleMapGroup - ) => { - if (isWaffleMapGroupWithGroups(group)) { - return ( - - ); - } - if (isWaffleMapGroupWithNodes(group)) { - return ( - - ); - } - }; } ); -const WaffleMapOuterContiner = styled.div` - flex: 1 0 0%; - display: flex; - justify-content: center; - flex-direction: column; - overflow-x: hidden; - overflow-y: auto; +const CenteredEmptyPrompt = styled(EuiEmptyPrompt)` + align-self: center; `; -const WaffleMapInnerContainer = styled.div` - display: flex; - flex-direction: row; - flex-wrap: wrap; - justify-content: center; - align-content: flex-start; - padding: 10px; +const MainContainer = styled.div` + position: relative; + flex: 1 1 auto; `; -const CenteredEmptyPrompt = styled(EuiEmptyPrompt)` - align-self: center; +const TableContainer = styled.div` + padding: ${props => props.theme.eui.paddingSizes.l}; +`; + +const ViewSwitcherContainer = styled.div` + padding: ${props => props.theme.eui.paddingSizes.l}; +`; + +const MapContainer = styled.div` + position: absolute; + display: flex; + top: 0; + right: 0; + bottom: 0; + left: 0; `; diff --git a/x-pack/plugins/infra/public/components/nodes_overview/table.tsx b/x-pack/plugins/infra/public/components/nodes_overview/table.tsx new file mode 100644 index 0000000000000..ac188d5e0d169 --- /dev/null +++ b/x-pack/plugins/infra/public/components/nodes_overview/table.tsx @@ -0,0 +1,167 @@ +/* + * 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 { EuiButtonEmpty, EuiInMemoryTable, EuiToolTip } from '@elastic/eui'; +import { InjectedIntl, injectI18n } from '@kbn/i18n/react'; +import { last } from 'lodash'; +import React from 'react'; +import { InfraNodeType } from '../../../server/lib/adapters/nodes'; +import { createWaffleMapNode } from '../../containers/waffle/nodes_to_wafflemap'; +import { InfraNode, InfraNodePath, InfraTimerangeInput } from '../../graphql/types'; +import { InfraWaffleMapNode, InfraWaffleMapOptions } from '../../lib/lib'; +import { fieldToName } from '../waffle/lib/field_to_display_name'; +import { NodeContextMenu } from '../waffle/node_context_menu'; + +interface Props { + nodes: InfraNode[]; + nodeType: InfraNodeType; + options: InfraWaffleMapOptions; + formatter: (subject: string | number) => string; + timeRange: InfraTimerangeInput; + intl: InjectedIntl; + onFilter: (filter: string) => void; +} + +const initialState = { + isPopoverOpen: [] as string[], +}; + +type State = Readonly; + +const getGroupPaths = (path: InfraNodePath[]) => { + switch (path.length) { + case 3: + return path.slice(0, 2); + case 2: + return path.slice(0, 1); + default: + return []; + } +}; + +export const TableView = injectI18n( + class extends React.PureComponent { + public readonly state: State = initialState; + public render() { + const { nodes, options, formatter, intl, timeRange, nodeType } = this.props; + const columns = [ + { + field: 'name', + name: intl.formatMessage({ + id: 'xpack.infra.tableView.columnName.name', + defaultMessage: 'Name', + }), + sortable: true, + truncateText: true, + textOnly: true, + render: (value: string, item: { node: InfraWaffleMapNode }) => ( + + + {value} + + + ), + }, + ...options.groupBy.map((grouping, index) => ({ + field: `group_${index}`, + name: fieldToName((grouping && grouping.field) || '', intl), + sortable: true, + truncateText: true, + textOnly: true, + render: (value: string) => { + const handleClick = () => this.props.onFilter(`${grouping.field}:"${value}"`); + return ( + + {value} + + ); + }, + })), + { + field: 'value', + name: intl.formatMessage({ + id: 'xpack.infra.tableView.columnName.last1m', + defaultMessage: 'Last 1m', + }), + sortable: true, + truncateText: true, + dataType: 'number', + render: (value: number) => {formatter(value)}, + }, + { + field: 'avg', + name: intl.formatMessage({ + id: 'xpack.infra.tableView.columnName.avg', + defaultMessage: 'Avg', + }), + sortable: true, + truncateText: true, + dataType: 'number', + render: (value: number) => {formatter(value)}, + }, + { + field: 'max', + name: intl.formatMessage({ + id: 'xpack.infra.tableView.columnName.max', + defaultMessage: 'Max', + }), + sortable: true, + truncateText: true, + dataType: 'number', + render: (value: number) => {formatter(value)}, + }, + ]; + const items = nodes.map(node => { + const name = last(node.path); + return { + name: (name && name.value) || 'unknown', + ...getGroupPaths(node.path).reduce( + (acc, path, index) => ({ + ...acc, + [`group_${index}`]: path.label, + }), + {} + ), + value: node.metric.value, + avg: node.metric.avg, + max: node.metric.max, + node: createWaffleMapNode(node), + }; + }); + const initialSorting = { + sort: { + field: 'value', + direction: 'desc', + }, + }; + return ( + + ); + } + + private openPopoverFor = (id: string) => () => { + this.setState(prevState => ({ isPopoverOpen: [...prevState.isPopoverOpen, id] })); + }; + + private closePopoverFor = (id: string) => () => { + this.setState(prevState => ({ + isPopoverOpen: prevState.isPopoverOpen.filter(subject => subject !== id), + })); + }; + } +); diff --git a/x-pack/plugins/infra/public/components/waffle/custom_field_panel.tsx b/x-pack/plugins/infra/public/components/waffle/custom_field_panel.tsx new file mode 100644 index 0000000000000..0c676e587a774 --- /dev/null +++ b/x-pack/plugins/infra/public/components/waffle/custom_field_panel.tsx @@ -0,0 +1,78 @@ +/* + * 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 { EuiButton, EuiComboBox, EuiForm, EuiFormRow } from '@elastic/eui'; +import { InjectedIntl, injectI18n } from '@kbn/i18n/react'; +import React from 'react'; +import { InfraIndexField } from 'x-pack/plugins/infra/server/graphql/types'; +interface Props { + onSubmit: (field: string) => void; + fields: InfraIndexField[]; + intl: InjectedIntl; +} + +interface SelectedOption { + label: string; +} + +const initialState = { + selectedOptions: [] as SelectedOption[], +}; + +type State = Readonly; + +export const CustomFieldPanel = injectI18n( + class extends React.PureComponent { + public static displayName = 'CustomFieldPanel'; + public readonly state: State = initialState; + public render() { + const { fields, intl } = this.props; + const options = fields + .filter(f => f.aggregatable && f.type === 'string') + .map(f => ({ label: f.name })); + return ( +
+ + + + + + Add + + +
+ ); + } + + private handleSubmit = () => { + this.props.onSubmit(this.state.selectedOptions[0].label); + }; + + private handleFieldSelection = (selectedOptions: SelectedOption[]) => { + this.setState({ selectedOptions }); + }; + } +); diff --git a/x-pack/plugins/infra/public/components/waffle/lib/field_to_display_name.ts b/x-pack/plugins/infra/public/components/waffle/lib/field_to_display_name.ts new file mode 100644 index 0000000000000..58f47b4ff46cf --- /dev/null +++ b/x-pack/plugins/infra/public/components/waffle/lib/field_to_display_name.ts @@ -0,0 +1,45 @@ +/* + * 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 { InjectedIntl } from '@kbn/i18n/react'; + +interface Lookup { + [id: string]: string; +} + +export const fieldToName = (field: string, intl: InjectedIntl) => { + const LOOKUP: Lookup = { + 'kubernetes.namespace': intl.formatMessage({ + id: 'xpack.infra.groupByDisplayNames.kubernetesNamespace', + defaultMessage: 'Namespace', + }), + 'kubernetes.node.name': intl.formatMessage({ + id: 'xpack.infra.groupByDisplayNames.kubernetesNodeName', + defaultMessage: 'Node', + }), + 'host.name': intl.formatMessage({ + id: 'xpack.infra.groupByDisplayNames.hostName', + defaultMessage: 'Host', + }), + 'meta.cloud.availability_zone': intl.formatMessage({ + id: 'xpack.infra.groupByDisplayNames.availabilityZone', + defaultMessage: 'Availability Zone', + }), + 'meta.cloud.machine_type': intl.formatMessage({ + id: 'xpack.infra.groupByDisplayNames.machineType', + defaultMessage: 'Machine Type', + }), + 'meta.cloud.project_id': intl.formatMessage({ + id: 'xpack.infra.groupByDisplayNames.projectID', + defaultMessage: 'Project ID', + }), + 'meta.cloud.provider': intl.formatMessage({ + id: 'xpack.infra.groupByDisplayNames.provider', + defaultMessage: 'Cloud Provider', + }), + }; + return LOOKUP[field] || field; +}; diff --git a/x-pack/plugins/infra/public/components/waffle/map.tsx b/x-pack/plugins/infra/public/components/waffle/map.tsx new file mode 100644 index 0000000000000..0bd4c5a8e388b --- /dev/null +++ b/x-pack/plugins/infra/public/components/waffle/map.tsx @@ -0,0 +1,107 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React from 'react'; +import styled from 'styled-components'; +import { nodesToWaffleMap } from '../../containers/waffle/nodes_to_wafflemap'; +import { + isWaffleMapGroupWithGroups, + isWaffleMapGroupWithNodes, +} from '../../containers/waffle/type_guards'; +import { InfraNode, InfraNodeType, InfraTimerangeInput } from '../../graphql/types'; +import { InfraWaffleMapBounds, InfraWaffleMapOptions } from '../../lib/lib'; +import { AutoSizer } from '../auto_sizer'; +import { GroupOfGroups } from './group_of_groups'; +import { GroupOfNodes } from './group_of_nodes'; +import { Legend } from './legend'; +import { applyWaffleMapLayout } from './lib/apply_wafflemap_layout'; + +interface Props { + nodes: InfraNode[]; + nodeType: InfraNodeType; + options: InfraWaffleMapOptions; + formatter: (subject: string | number) => string; + timeRange: InfraTimerangeInput; + onFilter: (filter: string) => void; + bounds: InfraWaffleMapBounds; +} + +export const Map: React.SFC = ({ + nodes, + options, + timeRange, + onFilter, + formatter, + bounds, + nodeType, +}) => { + const map = nodesToWaffleMap(nodes); + return ( + + {({ measureRef, content: { width = 0, height = 0 } }) => { + const groupsWithLayout = applyWaffleMapLayout(map, width, height); + return ( + measureRef(el)} + data-test-subj="waffleMap" + > + + {groupsWithLayout.map(group => { + if (isWaffleMapGroupWithGroups(group)) { + return ( + + ); + } + if (isWaffleMapGroupWithNodes(group)) { + return ( + + ); + } + })} + + + + ); + }} + + ); +}; + +const WaffleMapOuterContainer = styled.div` + flex: 1 0 0%; + display: flex; + justify-content: center; + flex-direction: column; + overflow-x: hidden; + overflow-y: auto; +`; + +const WaffleMapInnerContainer = styled.div` + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-content: center; + align-content: flex-start; + padding: 10px; +`; diff --git a/x-pack/plugins/infra/public/components/waffle/view_switcher.tsx b/x-pack/plugins/infra/public/components/waffle/view_switcher.tsx new file mode 100644 index 0000000000000..83952ff8f5c1f --- /dev/null +++ b/x-pack/plugins/infra/public/components/waffle/view_switcher.tsx @@ -0,0 +1,48 @@ +/* + * 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 { EuiButtonGroup } from '@elastic/eui'; +import { InjectedIntl, injectI18n } from '@kbn/i18n/react'; +import React from 'react'; + +interface Props { + view: string; + onChange: (view: string) => void; + intl: InjectedIntl; +} + +export const ViewSwitcher = injectI18n(({ view, onChange, intl }: Props) => { + const buttons = [ + { + id: 'map', + label: intl.formatMessage({ + id: 'xpack.infra.viewSwitcher.mapViewLabel', + defaultMessage: 'Map View', + }), + iconType: 'apps', + }, + { + id: 'table', + label: intl.formatMessage({ + id: 'xpack.infra.viewSwitcher.tableViewLabel', + defaultMessage: 'Table View', + }), + iconType: 'editorUnorderedList', + }, + ]; + return ( + + ); +}); diff --git a/x-pack/plugins/infra/public/components/waffle/waffle_group_by_controls.tsx b/x-pack/plugins/infra/public/components/waffle/waffle_group_by_controls.tsx index e492e153c67a0..7b58bed4d69b2 100644 --- a/x-pack/plugins/infra/public/components/waffle/waffle_group_by_controls.tsx +++ b/x-pack/plugins/infra/public/components/waffle/waffle_group_by_controls.tsx @@ -8,122 +8,56 @@ import { EuiBadge, EuiContextMenu, EuiContextMenuPanelDescriptor, + EuiContextMenuPanelItemDescriptor, EuiFilterButton, EuiFilterGroup, EuiPopover, } from '@elastic/eui'; import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react'; import React from 'react'; -import { InfraNodeType, InfraPathInput, InfraPathType } from '../../graphql/types'; +import { InfraIndexField, InfraNodeType, InfraPathInput, InfraPathType } from '../../graphql/types'; +import { InfraGroupByOptions } from '../../lib/lib'; +import { CustomFieldPanel } from './custom_field_panel'; +import { fieldToName } from './lib/field_to_display_name'; interface Props { nodeType: InfraNodeType; groupBy: InfraPathInput[]; onChange: (groupBy: InfraPathInput[]) => void; + onChangeCustomOptions: (options: InfraGroupByOptions[]) => void; + fields: InfraIndexField[]; intl: InjectedIntl; + customOptions: InfraGroupByOptions[]; } -let OPTIONS: { [P in InfraNodeType]: Array<{ text: string; type: InfraPathType; field: string }> }; +const createFieldToOptionMapper = (intl: InjectedIntl) => (field: string) => ({ + text: fieldToName(field, intl), + type: InfraPathType.terms, + field, +}); + +let OPTIONS: { [P in InfraNodeType]: InfraGroupByOptions[] }; const getOptions = ( nodeType: InfraNodeType, intl: InjectedIntl ): Array<{ text: string; type: InfraPathType; field: string }> => { if (!OPTIONS) { + const mapFieldToOption = createFieldToOptionMapper(intl); OPTIONS = { - [InfraNodeType.pod]: [ - { - text: intl.formatMessage({ - id: 'xpack.infra.waffle.podGroupByOptions.namespaceLabel', - defaultMessage: 'Namespace', - }), - type: InfraPathType.terms, - field: 'kubernetes.namespace', - }, - { - text: intl.formatMessage({ - id: 'xpack.infra.waffle.podGroupByOptions.nodeLabel', - defaultMessage: 'Node', - }), - type: InfraPathType.terms, - field: 'kubernetes.node.name', - }, - ], + [InfraNodeType.pod]: ['kubernetes.namespace', 'kubernetes.node.name'].map(mapFieldToOption), [InfraNodeType.container]: [ - { - text: intl.formatMessage({ - id: 'xpack.infra.waffle.containerGroupByOptions.hostLabel', - defaultMessage: 'Host', - }), - type: InfraPathType.terms, - field: 'host.name', - }, - { - text: intl.formatMessage({ - id: 'xpack.infra.waffle.containerGroupByOptions.availabilityZoneLabel', - defaultMessage: 'Availability Zone', - }), - type: InfraPathType.terms, - field: 'meta.cloud.availability_zone', - }, - { - text: intl.formatMessage({ - id: 'xpack.infra.waffle.containerGroupByOptions.machineTypeLabel', - defaultMessage: 'Machine Type', - }), - type: InfraPathType.terms, - field: 'meta.cloud.machine_type', - }, - { - text: intl.formatMessage({ - id: 'xpack.infra.waffle.containerGroupByOptions.projectIDLabel', - defaultMessage: 'Project ID', - }), - type: InfraPathType.terms, - field: 'meta.cloud.project_id', - }, - { - text: intl.formatMessage({ - id: 'xpack.infra.waffle.containerGroupByOptions.providerLabel', - defaultMessage: 'Provider', - }), - type: InfraPathType.terms, - field: 'meta.cloud.provider', - }, - ], + 'host.name', + 'meta.cloud.availability_zone', + 'meta.cloud.machine_type', + 'meta.cloud.project_id', + 'meta.cloud.provider', + ].map(mapFieldToOption), [InfraNodeType.host]: [ - { - text: intl.formatMessage({ - id: 'xpack.infra.waffle.hostGroupByOptions.availabilityZoneLabel', - defaultMessage: 'Availability Zone', - }), - type: InfraPathType.terms, - field: 'meta.cloud.availability_zone', - }, - { - text: intl.formatMessage({ - id: 'xpack.infra.waffle.hostGroupByOptions.machineTypeLabel', - defaultMessage: 'Machine Type', - }), - type: InfraPathType.terms, - field: 'meta.cloud.machine_type', - }, - { - text: intl.formatMessage({ - id: 'xpack.infra.waffle.hostGroupByOptions.projectIDLabel', - defaultMessage: 'Project ID', - }), - type: InfraPathType.terms, - field: 'meta.cloud.project_id', - }, - { - text: intl.formatMessage({ - id: 'xpack.infra.waffle.hostGroupByOptions.cloudProviderLabel', - defaultMessage: 'Cloud Provider', - }), - type: InfraPathType.terms, - field: 'meta.cloud.provider', - }, - ], + 'meta.cloud.availability_zone', + 'meta.cloud.machine_type', + 'meta.cloud.project_id', + 'meta.cloud.provider', + ].map(mapFieldToOption), }; } @@ -143,7 +77,7 @@ export const WaffleGroupByControls = injectI18n( public render() { const { nodeType, groupBy, intl } = this.props; - const options = getOptions(nodeType, intl); + const options = getOptions(nodeType, intl).concat(this.props.customOptions); if (!options.length) { throw Error( @@ -165,11 +99,35 @@ export const WaffleGroupByControls = injectI18n( id: 'xpack.infra.waffle.selectTwoGroupingsTitle', defaultMessage: 'Select up to two groupings', }), - items: options.map(o => { - const icon = groupBy.some(g => g.field === o.field) ? 'check' : 'empty'; - const panel = { name: o.text, onClick: this.handleClick(o.field), icon }; - return panel; + items: [ + { + name: intl.formatMessage({ + id: 'xpack.infra.waffle.customGroupByOptionName', + defaultMessage: 'Custom Field', + }), + icon: 'empty', + panel: 'customPanel', + }, + ...options.map(o => { + const icon = groupBy.some(g => g.field === o.field) ? 'check' : 'empty'; + const panel = { + name: o.text, + onClick: this.handleClick(o.field), + icon, + } as EuiContextMenuPanelItemDescriptor; + return panel; + }), + ], + }, + { + id: 'customPanel', + title: intl.formatMessage({ + id: 'xpack.infra.waffle.customGroupByPanelTitle', + defaultMessage: 'Group By Custom Field', }), + content: ( + + ), }, ]; const buttonBody = @@ -228,6 +186,8 @@ export const WaffleGroupByControls = injectI18n( private handleRemove = (field: string) => () => { const { groupBy } = this.props; this.props.onChange(groupBy.filter(g => g.field !== field)); + const options = this.props.customOptions.filter(g => g.field !== field); + this.props.onChangeCustomOptions(options); // We need to close the panel after we rmeove the pill icon otherwise // it will remain open because the click is still captured by the EuiFilterButton setTimeout(() => this.handleClose()); @@ -241,6 +201,20 @@ export const WaffleGroupByControls = injectI18n( this.setState(state => ({ isPopoverOpen: !state.isPopoverOpen })); }; + private handleCustomField = (field: string) => { + const options = [ + ...this.props.customOptions, + { + text: field, + field, + type: InfraPathType.custom, + }, + ]; + this.props.onChangeCustomOptions(options); + const fn = this.handleClick(field); + fn(); + }; + private handleClick = (field: string) => () => { const { groupBy } = this.props; if (groupBy.some(g => g.field === field)) { diff --git a/x-pack/plugins/infra/public/containers/logs/flyout_item.gql_query.ts b/x-pack/plugins/infra/public/containers/logs/flyout_item.gql_query.ts new file mode 100644 index 0000000000000..56b63a54b1126 --- /dev/null +++ b/x-pack/plugins/infra/public/containers/logs/flyout_item.gql_query.ts @@ -0,0 +1,23 @@ +/* + * 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 gql from 'graphql-tag'; + +export const flyoutItemQuery = gql` + query FlyoutItemQuery($sourceId: ID!, $itemId: ID!) { + source(id: $sourceId) { + id + logItem(id: $itemId) { + id + index + fields { + field + value + } + } + } + } +`; diff --git a/x-pack/plugins/infra/public/containers/logs/with_log_flyout.tsx b/x-pack/plugins/infra/public/containers/logs/with_log_flyout.tsx new file mode 100644 index 0000000000000..71c0a330fcb97 --- /dev/null +++ b/x-pack/plugins/infra/public/containers/logs/with_log_flyout.tsx @@ -0,0 +1,60 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { Query } from 'react-apollo'; +import { FlyoutItemQuery, InfraLogItem } from '../../graphql/types'; +import { FlyoutVisibility } from '../../store/local/log_flyout'; +import { WithOptions } from '../with_options'; +import { flyoutItemQuery } from './flyout_item.gql_query'; +import { WithFlyoutOptions } from './with_log_flyout_options'; + +interface WithFlyoutArgs { + flyoutItem: InfraLogItem | null; + setFlyoutItem: (id: string) => void; + showFlyout: () => void; + hideFlyout: () => void; + error?: string | undefined; + loading: boolean; +} + +interface WithFlyoutProps { + children: (args: WithFlyoutArgs) => React.ReactNode; +} + +export const WithLogFlyout = ({ children }: WithFlyoutProps) => { + return ( + + {({ sourceId }) => ( + + {({ showFlyout, hideFlyout, setFlyoutItem, flyoutId, flyoutVisibility }) => + flyoutVisibility === FlyoutVisibility.visible ? ( + + query={flyoutItemQuery} + fetchPolicy="no-cache" + variables={{ + itemId: (flyoutId != null && flyoutId) || '', + sourceId, + }} + > + {({ data, error, loading }) => { + return children({ + showFlyout, + hideFlyout, + setFlyoutItem, + flyoutItem: (data && data.source && data.source.logItem) || null, + error: error && error.message, + loading, + }); + }} + + ) : null + } + + )} + + ); +}; diff --git a/x-pack/plugins/infra/public/containers/logs/with_log_flyout_options.tsx b/x-pack/plugins/infra/public/containers/logs/with_log_flyout_options.tsx new file mode 100644 index 0000000000000..13e011cda876b --- /dev/null +++ b/x-pack/plugins/infra/public/containers/logs/with_log_flyout_options.tsx @@ -0,0 +1,105 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { connect } from 'react-redux'; +import { createSelector } from 'reselect'; + +import { isString } from 'lodash'; +import { flyoutOptionsActions, flyoutOptionsSelectors, State } from '../../store'; +import { FlyoutVisibility } from '../../store/local/log_flyout'; +import { asChildFunctionRenderer } from '../../utils/typed_react'; +import { bindPlainActionCreators } from '../../utils/typed_redux'; +import { UrlStateContainer } from '../../utils/url_state'; + +const selectOptionsUrlState = createSelector( + flyoutOptionsSelectors.selectFlyoutId, + flyoutOptionsSelectors.selectFlyoutVisibility, + (flyoutId, flyoutVisibility) => ({ + flyoutVisibility, + flyoutId, + }) +); + +export const withFlyoutOptions = connect( + (state: State) => ({ + flyoutVisibility: flyoutOptionsSelectors.selectFlyoutVisibility(state), + flyoutId: flyoutOptionsSelectors.selectFlyoutId(state), + urlState: selectOptionsUrlState(state), + }), + bindPlainActionCreators({ + setFlyoutItem: flyoutOptionsActions.setFlyoutItem, + showFlyout: flyoutOptionsActions.showFlyout, + hideFlyout: flyoutOptionsActions.hideFlyout, + }) +); + +export const WithFlyoutOptions = asChildFunctionRenderer(withFlyoutOptions); + +/** + * Url State + */ + +interface FlyoutOptionsUrlState { + flyoutId?: ReturnType; + flyoutVisibility?: ReturnType; +} + +export const WithFlyoutOptionsUrlState = () => ( + + {({ setFlyoutItem, showFlyout, hideFlyout, urlState }) => ( + { + if (newUrlState && newUrlState.flyoutId) { + setFlyoutItem(newUrlState.flyoutId); + } + if (newUrlState && newUrlState.flyoutVisibility === FlyoutVisibility.visible) { + showFlyout(); + } + if (newUrlState && newUrlState.flyoutVisibility === FlyoutVisibility.hidden) { + hideFlyout(); + } + }} + onInitialize={initialUrlState => { + if (initialUrlState && initialUrlState.flyoutId) { + setFlyoutItem(initialUrlState.flyoutId); + } + if (initialUrlState && initialUrlState.flyoutVisibility === FlyoutVisibility.visible) { + showFlyout(); + } + if (initialUrlState && initialUrlState.flyoutVisibility === FlyoutVisibility.hidden) { + hideFlyout(); + } + }} + /> + )} + +); + +const mapToUrlState = (value: any): FlyoutOptionsUrlState | undefined => + value + ? { + flyoutId: mapToFlyoutIdState(value.flyoutId), + flyoutVisibility: mapToFlyoutVisibilityState(value.flyoutVisibility), + } + : undefined; + +const mapToFlyoutIdState = (subject: any) => { + return subject && isString(subject) ? subject : undefined; +}; +const mapToFlyoutVisibilityState = (subject: any) => { + if (subject) { + if (subject === 'visible') { + return FlyoutVisibility.visible; + } + if (subject === 'hidden') { + return FlyoutVisibility.hidden; + } + } +}; diff --git a/x-pack/plugins/infra/public/containers/waffle/nodes_to_wafflemap.ts b/x-pack/plugins/infra/public/containers/waffle/nodes_to_wafflemap.ts index 56f2ffa0bc5d9..4e1f0152b7f4f 100644 --- a/x-pack/plugins/infra/public/containers/waffle/nodes_to_wafflemap.ts +++ b/x-pack/plugins/infra/public/containers/waffle/nodes_to_wafflemap.ts @@ -16,7 +16,7 @@ import { } from '../../lib/lib'; import { isWaffleMapGroupWithGroups, isWaffleMapGroupWithNodes } from './type_guards'; -function createId(path: InfraNodePath[]) { +export function createId(path: InfraNodePath[]) { return path.map(p => p.value).join('/'); } @@ -40,6 +40,7 @@ function findOrCreateGroupWithNodes( } } } + const lastPath = last(path); const existingGroup = groups.find(g => g.id === id); if (isWaffleMapGroupWithNodes(existingGroup)) { return existingGroup; @@ -51,7 +52,7 @@ function findOrCreateGroupWithNodes( ? i18n.translate('xpack.infra.nodesToWaffleMap.groupsWithNodes.allName', { defaultMessage: 'All', }) - : last(path).label, + : (lastPath && lastPath.label) || 'Unknown Group', count: 0, width: 0, squareSize: 0, @@ -64,6 +65,7 @@ function findOrCreateGroupWithGroups( path: InfraNodePath[] ): InfraWaffleMapGroupOfGroups { const id = path.length === 0 ? '__all__' : createId(path); + const lastPath = last(path); const existingGroup = groups.find(g => g.id === id); if (isWaffleMapGroupWithGroups(existingGroup)) { return existingGroup; @@ -75,7 +77,7 @@ function findOrCreateGroupWithGroups( ? i18n.translate('xpack.infra.nodesToWaffleMap.groupsWithGroups.allName', { defaultMessage: 'All', }) - : last(path).label, + : (lastPath && lastPath.label) || 'Unknown Group', count: 0, width: 0, squareSize: 0, @@ -83,13 +85,16 @@ function findOrCreateGroupWithGroups( }; } -function createWaffleMapNode(node: InfraNode): InfraWaffleMapNode { +export function createWaffleMapNode(node: InfraNode): InfraWaffleMapNode { const nodePathItem = last(node.path); + if (!nodePathItem) { + throw new Error('There must be at least one node path item'); + } return { pathId: node.path.map(p => p.value).join('/'), path: node.path, id: nodePathItem.value, - name: nodePathItem.label, + name: nodePathItem.label || nodePathItem.value, metric: node.metric, }; } diff --git a/x-pack/plugins/infra/public/containers/waffle/waffle_nodes.gql_query.ts b/x-pack/plugins/infra/public/containers/waffle/waffle_nodes.gql_query.ts index 51b67086a4bfa..8226cf9ad9b32 100644 --- a/x-pack/plugins/infra/public/containers/waffle/waffle_nodes.gql_query.ts +++ b/x-pack/plugins/infra/public/containers/waffle/waffle_nodes.gql_query.ts @@ -25,6 +25,8 @@ export const waffleNodesQuery = gql` metric { name value + avg + max } } } diff --git a/x-pack/plugins/infra/public/containers/waffle/with_waffle_nodes.tsx b/x-pack/plugins/infra/public/containers/waffle/with_waffle_nodes.tsx index 2f8a18ca45ab7..b1fdd3a457a54 100644 --- a/x-pack/plugins/infra/public/containers/waffle/with_waffle_nodes.tsx +++ b/x-pack/plugins/infra/public/containers/waffle/with_waffle_nodes.tsx @@ -9,18 +9,17 @@ import { Query } from 'react-apollo'; import { InfraMetricInput, + InfraNode, InfraNodeType, InfraPathInput, InfraPathType, InfraTimerangeInput, WaffleNodesQuery, } from '../../graphql/types'; -import { InfraWaffleMapGroup } from '../../lib/lib'; -import { nodesToWaffleMap } from './nodes_to_wafflemap'; import { waffleNodesQuery } from './waffle_nodes.gql_query'; interface WithWaffleNodesArgs { - nodes: InfraWaffleMapGroup[]; + nodes: InfraNode[]; loading: boolean; refetch: () => void; } @@ -67,7 +66,7 @@ export const WithWaffleNodes = ({ loading, nodes: data && data.source && data.source.map && data.source.map.nodes - ? nodesToWaffleMap(data.source.map.nodes) + ? data.source.map.nodes : [], refetch, }) diff --git a/x-pack/plugins/infra/public/containers/waffle/with_waffle_options.tsx b/x-pack/plugins/infra/public/containers/waffle/with_waffle_options.tsx index b03f1e003fc4b..b71cb00ed2dd4 100644 --- a/x-pack/plugins/infra/public/containers/waffle/with_waffle_options.tsx +++ b/x-pack/plugins/infra/public/containers/waffle/with_waffle_options.tsx @@ -14,6 +14,7 @@ import { InfraNodeType, InfraPathType, } from '../../graphql/types'; +import { InfraGroupByOptions } from '../../lib/lib'; import { State, waffleOptionsActions, waffleOptionsSelectors } from '../../store'; import { asChildFunctionRenderer } from '../../utils/typed_react'; import { bindPlainActionCreators } from '../../utils/typed_redux'; @@ -21,12 +22,16 @@ import { UrlStateContainer } from '../../utils/url_state'; const selectOptionsUrlState = createSelector( waffleOptionsSelectors.selectMetric, + waffleOptionsSelectors.selectView, waffleOptionsSelectors.selectGroupBy, waffleOptionsSelectors.selectNodeType, - (metric, groupBy, nodeType) => ({ + waffleOptionsSelectors.selectCustomOptions, + (metric, view, groupBy, nodeType, customOptions) => ({ metric, groupBy, nodeType, + view, + customOptions, }) ); @@ -35,12 +40,16 @@ export const withWaffleOptions = connect( metric: waffleOptionsSelectors.selectMetric(state), groupBy: waffleOptionsSelectors.selectGroupBy(state), nodeType: waffleOptionsSelectors.selectNodeType(state), + view: waffleOptionsSelectors.selectView(state), + customOptions: waffleOptionsSelectors.selectCustomOptions(state), urlState: selectOptionsUrlState(state), }), bindPlainActionCreators({ changeMetric: waffleOptionsActions.changeMetric, changeGroupBy: waffleOptionsActions.changeGroupBy, changeNodeType: waffleOptionsActions.changeNodeType, + changeView: waffleOptionsActions.changeView, + changeCustomOptions: waffleOptionsActions.changeCustomOptions, }) ); @@ -54,11 +63,20 @@ interface WaffleOptionsUrlState { metric?: ReturnType; groupBy?: ReturnType; nodeType?: ReturnType; + view?: ReturnType; + customOptions?: ReturnType; } export const WithWaffleOptionsUrlState = () => ( - {({ changeMetric, urlState, changeGroupBy, changeNodeType }) => ( + {({ + changeMetric, + urlState, + changeGroupBy, + changeNodeType, + changeView, + changeCustomOptions, + }) => ( ( if (newUrlState && newUrlState.nodeType) { changeNodeType(newUrlState.nodeType); } + if (newUrlState && newUrlState.view) { + changeView(newUrlState.view); + } + if (newUrlState && newUrlState.customOptions) { + changeCustomOptions(newUrlState.customOptions); + } }} onInitialize={initialUrlState => { if (initialUrlState && initialUrlState.metric) { @@ -84,6 +108,12 @@ export const WithWaffleOptionsUrlState = () => ( if (initialUrlState && initialUrlState.nodeType) { changeNodeType(initialUrlState.nodeType); } + if (initialUrlState && initialUrlState.view) { + changeView(initialUrlState.view); + } + if (initialUrlState && initialUrlState.customOptions) { + changeCustomOptions(initialUrlState.customOptions); + } }} /> )} @@ -96,6 +126,8 @@ const mapToUrlState = (value: any): WaffleOptionsUrlState | undefined => metric: mapToMetricUrlState(value.metric), groupBy: mapToGroupByUrlState(value.groupBy), nodeType: mapToNodeTypeUrlState(value.nodeType), + view: mapToViewUrlState(value.view), + customOptions: mapToCustomOptionsUrlState(value.customOptions), } : undefined; @@ -107,6 +139,15 @@ const isInfraPathInput = (subject: any): subject is InfraPathType => { return subject != null && subject.type != null && InfraPathType[subject.type] != null; }; +const isInfraGroupByOption = (subject: any): subject is InfraGroupByOptions => { + return ( + subject != null && + subject.text != null && + subject.field != null && + InfraPathType[subject.type] != null + ); +}; + const mapToMetricUrlState = (subject: any) => { return subject && isInfraMetricInput(subject) ? subject : undefined; }; @@ -118,3 +159,13 @@ const mapToGroupByUrlState = (subject: any) => { const mapToNodeTypeUrlState = (subject: any) => { return subject && InfraNodeType[subject] ? subject : undefined; }; + +const mapToViewUrlState = (subject: any) => { + return subject && ['map', 'table'].includes(subject) ? subject : undefined; +}; + +const mapToCustomOptionsUrlState = (subject: any) => { + return subject && Array.isArray(subject) && subject.every(isInfraGroupByOption) + ? subject + : undefined; +}; diff --git a/x-pack/plugins/infra/public/graphql/introspection.json b/x-pack/plugins/infra/public/graphql/introspection.json index eb04a6d14e227..e64b567aa88ef 100644 --- a/x-pack/plugins/infra/public/graphql/introspection.json +++ b/x-pack/plugins/infra/public/graphql/introspection.json @@ -11,7 +11,7 @@ "fields": [ { "name": "source", - "description": "Get an infrastructure data source by id.\n\nThe resolution order for the source configuration attributes is as follows\nwith the first defined value winning:\n\n1. The attributes of the saved object with the given 'id'.\n2. The attributes defined in the static Kibana configuration key\n 'xpack.infra.sources.default'.\n3. The hard-coded default values.\n\nAs a consequence, querying a source without a corresponding saved object\ndoesn't error out, but returns the configured or hardcoded defaults.", + "description": "Get an infrastructure data source by id.\n\nThe resolution order for the source configuration attributes is as follows\nwith the first defined value winning:\n\n1. The attributes of the saved object with the given 'id'.\n2. The attributes defined in the static Kibana configuration key\n 'xpack.infra.sources.default'.\n3. The hard-coded default values.\n\nAs a consequence, querying a source that doesn't exist doesn't error out,\nbut returns the configured or hardcoded defaults.", "args": [ { "name": "id", @@ -299,6 +299,29 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "logItem", + "description": "", + "args": [ + { + "name": "id", + "description": "", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "ID", "ofType": null } + }, + "defaultValue": null + } + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "OBJECT", "name": "InfraLogItem", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "map", "description": "A hierarchy of hosts, pods, containers, services or arbitrary groups", @@ -1310,6 +1333,96 @@ "enumValues": null, "possibleTypes": null }, + { + "kind": "OBJECT", + "name": "InfraLogItem", + "description": "", + "fields": [ + { + "name": "id", + "description": "The ID of the document", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "ID", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "index", + "description": "The index where the document was found", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "fields", + "description": "An array of flattened fields and values", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "OBJECT", "name": "InfraLogItemField", "ofType": null } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "InfraLogItemField", + "description": "", + "fields": [ + { + "name": "field", + "description": "The flattened field name", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "value", + "description": "The value for the Field as a string", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, { "kind": "INPUT_OBJECT", "name": "InfraTimerangeInput", @@ -1480,7 +1593,8 @@ "description": "", "isDeprecated": false, "deprecationReason": null - } + }, + { "name": "custom", "description": "", "isDeprecated": false, "deprecationReason": null } ], "possibleTypes": null }, @@ -1660,6 +1774,30 @@ }, "isDeprecated": false, "deprecationReason": null + }, + { + "name": "avg", + "description": "", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "max", + "description": "", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null } ], "inputFields": null, diff --git a/x-pack/plugins/infra/public/graphql/types.ts b/x-pack/plugins/infra/public/graphql/types.ts index eff2698106f43..d96719aef0059 100644 --- a/x-pack/plugins/infra/public/graphql/types.ts +++ b/x-pack/plugins/infra/public/graphql/types.ts @@ -9,7 +9,7 @@ // ==================================================== export interface Query { - /** Get an infrastructure data source by id.The resolution order for the source configuration attributes is as followswith the first defined value winning:1. The attributes of the saved object with the given 'id'.2. The attributes defined in the static Kibana configuration key'xpack.infra.sources.default'.3. The hard-coded default values.As a consequence, querying a source without a corresponding saved objectdoesn't error out, but returns the configured or hardcoded defaults. */ + /** Get an infrastructure data source by id.The resolution order for the source configuration attributes is as followswith the first defined value winning:1. The attributes of the saved object with the given 'id'.2. The attributes defined in the static Kibana configuration key'xpack.infra.sources.default'.3. The hard-coded default values.As a consequence, querying a source that doesn't exist doesn't error out,but returns the configured or hardcoded defaults. */ source: InfraSource; /** Get a list of all infrastructure data sources */ allSources: InfraSource[]; @@ -34,6 +34,8 @@ export interface InfraSource { logEntriesBetween: InfraLogEntryInterval; /** A consecutive span of summary buckets within an interval */ logSummaryBetween: InfraLogSummaryInterval; + + logItem: InfraLogItem; /** A hierarchy of hosts, pods, containers, services or arbitrary groups */ map?: InfraResponse | null; @@ -177,6 +179,22 @@ export interface InfraLogSummaryBucket { entriesCount: number; } +export interface InfraLogItem { + /** The ID of the document */ + id: string; + /** The index where the document was found */ + index: string; + /** An array of flattened fields and values */ + fields: InfraLogItemField[]; +} + +export interface InfraLogItemField { + /** The flattened field name */ + field: string; + /** The value for the Field as a string */ + value: string; +} + export interface InfraResponse { nodes: InfraNode[]; } @@ -197,6 +215,10 @@ export interface InfraNodeMetric { name: InfraMetricType; value: number; + + avg: number; + + max: number; } export interface InfraMetricData { @@ -395,6 +417,9 @@ export interface LogSummaryBetweenInfraSourceArgs { /** The query to filter the log entries by */ filterQuery?: string | null; } +export interface LogItemInfraSourceArgs { + id: string; +} export interface MapInfraSourceArgs { timerange: InfraTimerangeInput; @@ -456,6 +481,7 @@ export enum InfraPathType { hosts = 'hosts', pods = 'pods', containers = 'containers', + custom = 'custom', } export enum InfraMetricType { @@ -521,6 +547,45 @@ export type InfraLogMessageSegment = InfraLogMessageFieldSegment | InfraLogMessa // Documents // ==================================================== +export namespace FlyoutItemQuery { + export type Variables = { + sourceId: string; + itemId: string; + }; + + export type Query = { + __typename?: 'Query'; + + source: Source; + }; + + export type Source = { + __typename?: 'InfraSource'; + + id: string; + + logItem: LogItem; + }; + + export type LogItem = { + __typename?: 'InfraLogItem'; + + id: string; + + index: string; + + fields: Fields[]; + }; + + export type Fields = { + __typename?: 'InfraLogItemField'; + + field: string; + + value: string; + }; +} + export namespace MetadataQuery { export type Variables = { sourceId: string; @@ -658,6 +723,10 @@ export namespace WaffleNodesQuery { name: InfraMetricType; value: number; + + avg: number; + + max: number; }; } diff --git a/x-pack/plugins/infra/public/lib/lib.ts b/x-pack/plugins/infra/public/lib/lib.ts index 35c58c7576158..9819c55a5a281 100644 --- a/x-pack/plugins/infra/public/lib/lib.ts +++ b/x-pack/plugins/infra/public/lib/lib.ts @@ -15,6 +15,7 @@ import { InfraNodeMetric, InfraNodePath, InfraPathInput, + InfraPathType, InfraTimerangeInput, SourceQuery, } from '../graphql/types'; @@ -204,3 +205,9 @@ export enum InfraWaffleMapDataFormat { bitsBinaryJEDEC = 'bitsBinaryJEDEC', abbreviatedNumber = 'abbreviatedNumber', } + +export interface InfraGroupByOptions { + text: string; + type: InfraPathType; + field: string; +} diff --git a/x-pack/plugins/infra/public/pages/home/page_content.tsx b/x-pack/plugins/infra/public/pages/home/page_content.tsx index 2b6990a6dbe54..c409be860c062 100644 --- a/x-pack/plugins/infra/public/pages/home/page_content.tsx +++ b/x-pack/plugins/infra/public/pages/home/page_content.tsx @@ -6,8 +6,8 @@ import React from 'react'; +import { NodesOverview } from '../../components/nodes_overview'; import { PageContent } from '../../components/page'; -import { Waffle } from '../../components/waffle'; import { WithWaffleFilter } from '../../containers/waffle/with_waffle_filters'; import { WithWaffleNodes } from '../../containers/waffle/with_waffle_nodes'; @@ -27,7 +27,7 @@ export const HomePageContent: React.SFC = () => ( {({ currentTimeRange, isAutoReloading }) => ( - {({ metric, groupBy, nodeType }) => ( + {({ metric, groupBy, nodeType, view, changeView }) => ( ( timerange={currentTimeRange} > {({ nodes, loading, refetch }) => ( - 0 && isAutoReloading ? false : loading} nodeType={nodeType} options={{ ...wafflemap, metric, fields: configuredFields, groupBy }} reload={refetch} onDrilldown={applyFilterQuery} timeRange={currentTimeRange} + view={view} + onViewChange={changeView} /> )} diff --git a/x-pack/plugins/infra/public/pages/home/toolbar.tsx b/x-pack/plugins/infra/public/pages/home/toolbar.tsx index aa519564509cf..16ac4c0a19638 100644 --- a/x-pack/plugins/infra/public/pages/home/toolbar.tsx +++ b/x-pack/plugins/infra/public/pages/home/toolbar.tsx @@ -109,22 +109,41 @@ export const HomeToolbar = injectI18n(({ intl }) => ( )}
- - {({ changeMetric, changeGroupBy, groupBy, metric, nodeType }) => ( - - - - - - - - + + {({ derivedIndexPattern }) => ( + + {({ + changeMetric, + changeGroupBy, + changeCustomOptions, + customOptions, + groupBy, + metric, + nodeType, + }) => ( + + + + + + + + + )} + )} - + {({ currentTime, isAutoReloading, jumpToTime, startAutoReload, stopAutoReload }) => ( diff --git a/x-pack/plugins/infra/public/pages/logs/logs.tsx b/x-pack/plugins/infra/public/pages/logs/logs.tsx index 1b45d05de2842..11c0a7f038111 100644 --- a/x-pack/plugins/infra/public/pages/logs/logs.tsx +++ b/x-pack/plugins/infra/public/pages/logs/logs.tsx @@ -12,10 +12,14 @@ import { LogsToolbar } from './toolbar'; import { EmptyPage } from '../../components/empty_page'; import { Header } from '../../components/header'; +import { LogFlyout } from '../../components/logging/log_flyout'; import { ColumnarPage } from '../../components/page'; import { InfraHeaderFeedbackLink } from '../../components/header_feedback_link'; -import { WithLogFilterUrlState } from '../../containers/logs/with_log_filter'; +import { WithLogFilter, WithLogFilterUrlState } from '../../containers/logs/with_log_filter'; +import { WithLogFlyout } from '../../containers/logs/with_log_flyout'; +import { WithFlyoutOptions } from '../../containers/logs/with_log_flyout_options'; +import { WithFlyoutOptionsUrlState } from '../../containers/logs/with_log_flyout_options'; import { WithLogMinimapUrlState } from '../../containers/logs/with_log_minimap'; import { WithLogPositionUrlState } from '../../containers/logs/with_log_position'; import { WithLogTextviewUrlState } from '../../containers/logs/with_log_textview'; @@ -61,8 +65,32 @@ export const LogsPage = injectI18n( + - + + {({ applyFilterQueryFromKueryExpression }) => ( + + + {({ showFlyout, setFlyoutItem }) => ( + + )} + + + {({ flyoutItem, hideFlyout, loading }) => ( + + )} + + + )} + ) : isLoading ? ( diff --git a/x-pack/plugins/infra/public/pages/logs/page_content.tsx b/x-pack/plugins/infra/public/pages/logs/page_content.tsx index e2efd2f1a333a..2ca93a310323a 100644 --- a/x-pack/plugins/infra/public/pages/logs/page_content.tsx +++ b/x-pack/plugins/infra/public/pages/logs/page_content.tsx @@ -17,7 +17,12 @@ import { WithLogTextview } from '../../containers/logs/with_log_textview'; import { WithStreamItems } from '../../containers/logs/with_stream_items'; import { WithSummary } from '../../containers/logs/with_summary'; -export const LogsPageContent: React.SFC = () => ( +interface Props { + setFlyoutItem: (id: string) => void; + showFlyout: () => void; +} + +export const LogsPageContent: React.SFC = ({ showFlyout, setFlyoutItem }) => ( {({ measureRef, content: { width = 0, height = 0 } }) => ( @@ -57,6 +62,8 @@ export const LogsPageContent: React.SFC = () => ( target={targetPosition} width={width} wrap={wrap} + setFlyoutItem={setFlyoutItem} + showFlyout={showFlyout} /> )} diff --git a/x-pack/plugins/infra/public/store/actions.ts b/x-pack/plugins/infra/public/store/actions.ts index ee9a2858f1c34..68a20f4dba98f 100644 --- a/x-pack/plugins/infra/public/store/actions.ts +++ b/x-pack/plugins/infra/public/store/actions.ts @@ -13,5 +13,6 @@ export { waffleFilterActions, waffleTimeActions, waffleOptionsActions, + flyoutOptionsActions, } from './local'; export { logEntriesActions, logSummaryActions } from './remote'; diff --git a/x-pack/plugins/infra/public/store/local/actions.ts b/x-pack/plugins/infra/public/store/local/actions.ts index 8b9e0c9f5b58a..ef988c9b956a0 100644 --- a/x-pack/plugins/infra/public/store/local/actions.ts +++ b/x-pack/plugins/infra/public/store/local/actions.ts @@ -12,3 +12,4 @@ export { metricTimeActions } from './metric_time'; export { waffleFilterActions } from './waffle_filter'; export { waffleTimeActions } from './waffle_time'; export { waffleOptionsActions } from './waffle_options'; +export { flyoutOptionsActions } from './log_flyout'; diff --git a/x-pack/plugins/infra/public/store/local/log_flyout/actions.ts b/x-pack/plugins/infra/public/store/local/log_flyout/actions.ts new file mode 100644 index 0000000000000..f0d75030dce1f --- /dev/null +++ b/x-pack/plugins/infra/public/store/local/log_flyout/actions.ts @@ -0,0 +1,11 @@ +/* + * 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 actionCreatorFactory from 'typescript-fsa'; +const actionCreator = actionCreatorFactory('x-pack/infra/local/log_flyout'); +export const setFlyoutItem = actionCreator('SET_FLYOUT_ITEM'); +export const showFlyout = actionCreator('SHOW_FLYOUT'); +export const hideFlyout = actionCreator('HIDE_FLYOUT'); diff --git a/x-pack/plugins/infra/public/store/local/log_flyout/index.ts b/x-pack/plugins/infra/public/store/local/log_flyout/index.ts new file mode 100644 index 0000000000000..8dda5c3890ce6 --- /dev/null +++ b/x-pack/plugins/infra/public/store/local/log_flyout/index.ts @@ -0,0 +1,11 @@ +/* + * 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 * as flyoutOptionsActions from './actions'; +import * as flyoutOptionsSelectors from './selector'; + +export { flyoutOptionsActions, flyoutOptionsSelectors }; +export * from './reducer'; diff --git a/x-pack/plugins/infra/public/store/local/log_flyout/reducer.ts b/x-pack/plugins/infra/public/store/local/log_flyout/reducer.ts new file mode 100644 index 0000000000000..695dc5e7d49b7 --- /dev/null +++ b/x-pack/plugins/infra/public/store/local/log_flyout/reducer.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 { combineReducers } from 'redux'; +import { reducerWithInitialState } from 'typescript-fsa-reducers'; + +import { hideFlyout, setFlyoutItem, showFlyout } from './actions'; + +export enum FlyoutVisibility { + hidden = 'hidden', + visible = 'visible', +} + +export interface FlyoutOptionsState { + visibility: FlyoutVisibility; + itemId: string; +} + +export const initialFlyoutOptionsState: FlyoutOptionsState = { + visibility: FlyoutVisibility.hidden, + itemId: '', +}; + +const currentFlyoutReducer = reducerWithInitialState(initialFlyoutOptionsState.itemId).case( + setFlyoutItem, + (current, target) => target +); + +const currentFlyoutVisibilityReducer = reducerWithInitialState(initialFlyoutOptionsState.visibility) + .case(hideFlyout, () => FlyoutVisibility.hidden) + .case(showFlyout, () => FlyoutVisibility.visible); + +export const flyoutOptionsReducer = combineReducers({ + itemId: currentFlyoutReducer, + visibility: currentFlyoutVisibilityReducer, +}); diff --git a/x-pack/plugins/infra/public/store/local/log_flyout/selector.ts b/x-pack/plugins/infra/public/store/local/log_flyout/selector.ts new file mode 100644 index 0000000000000..5024290b7bdc7 --- /dev/null +++ b/x-pack/plugins/infra/public/store/local/log_flyout/selector.ts @@ -0,0 +1,10 @@ +/* + * 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 { FlyoutOptionsState } from './reducer'; + +export const selectFlyoutId = (state: FlyoutOptionsState) => state.itemId; +export const selectFlyoutVisibility = (state: FlyoutOptionsState) => state.visibility; diff --git a/x-pack/plugins/infra/public/store/local/reducer.ts b/x-pack/plugins/infra/public/store/local/reducer.ts index 59e890b748d5e..d7cff2f004f3f 100644 --- a/x-pack/plugins/infra/public/store/local/reducer.ts +++ b/x-pack/plugins/infra/public/store/local/reducer.ts @@ -7,6 +7,7 @@ import { combineReducers } from 'redux'; import { initialLogFilterState, logFilterReducer, LogFilterState } from './log_filter'; +import { flyoutOptionsReducer, FlyoutOptionsState, initialFlyoutOptionsState } from './log_flyout'; import { initialLogMinimapState, logMinimapReducer, LogMinimapState } from './log_minimap'; import { initialLogPositionState, logPositionReducer, LogPositionState } from './log_position'; import { initialLogTextviewState, logTextviewReducer, LogTextviewState } from './log_textview'; @@ -28,6 +29,7 @@ export interface LocalState { waffleFilter: WaffleFilterState; waffleTime: WaffleTimeState; waffleMetrics: WaffleOptionsState; + logFlyout: FlyoutOptionsState; } export const initialLocalState: LocalState = { @@ -39,6 +41,7 @@ export const initialLocalState: LocalState = { waffleFilter: initialWaffleFilterState, waffleTime: initialWaffleTimeState, waffleMetrics: initialWaffleOptionsState, + logFlyout: initialFlyoutOptionsState, }; export const localReducer = combineReducers({ @@ -50,4 +53,5 @@ export const localReducer = combineReducers({ waffleFilter: waffleFilterReducer, waffleTime: waffleTimeReducer, waffleMetrics: waffleOptionsReducer, + logFlyout: flyoutOptionsReducer, }); diff --git a/x-pack/plugins/infra/public/store/local/selectors.ts b/x-pack/plugins/infra/public/store/local/selectors.ts index 85188e144ade1..46e6fbafb686e 100644 --- a/x-pack/plugins/infra/public/store/local/selectors.ts +++ b/x-pack/plugins/infra/public/store/local/selectors.ts @@ -6,6 +6,7 @@ import { globalizeSelectors } from '../../utils/typed_redux'; import { logFilterSelectors as innerLogFilterSelectors } from './log_filter'; +import { flyoutOptionsSelectors as innerFlyoutOptionsSelectors } from './log_flyout'; import { logMinimapSelectors as innerLogMinimapSelectors } from './log_minimap'; import { logPositionSelectors as innerLogPositionSelectors } from './log_position'; import { logTextviewSelectors as innerLogTextviewSelectors } from './log_textview'; @@ -54,3 +55,8 @@ export const waffleOptionsSelectors = globalizeSelectors( (state: LocalState) => state.waffleMetrics, innerWaffleOptionsSelectors ); + +export const flyoutOptionsSelectors = globalizeSelectors( + (state: LocalState) => state.logFlyout, + innerFlyoutOptionsSelectors +); diff --git a/x-pack/plugins/infra/public/store/local/waffle_options/actions.ts b/x-pack/plugins/infra/public/store/local/waffle_options/actions.ts index 229b915db07db..5ef315e4b36d8 100644 --- a/x-pack/plugins/infra/public/store/local/waffle_options/actions.ts +++ b/x-pack/plugins/infra/public/store/local/waffle_options/actions.ts @@ -5,11 +5,13 @@ */ import actionCreatorFactory from 'typescript-fsa'; - import { InfraMetricInput, InfraNodeType, InfraPathInput } from '../../../graphql/types'; +import { InfraGroupByOptions } from '../../../lib/lib'; const actionCreator = actionCreatorFactory('x-pack/infra/local/waffle_options'); export const changeMetric = actionCreator('CHANGE_METRIC'); export const changeGroupBy = actionCreator('CHANGE_GROUP_BY'); +export const changeCustomOptions = actionCreator('CHANGE_CUSTOM_OPTIONS'); export const changeNodeType = actionCreator('CHANGE_NODE_TYPE'); +export const changeView = actionCreator('CHANGE_VIEW'); diff --git a/x-pack/plugins/infra/public/store/local/waffle_options/reducer.ts b/x-pack/plugins/infra/public/store/local/waffle_options/reducer.ts index f474462245f4d..d778eee60afb7 100644 --- a/x-pack/plugins/infra/public/store/local/waffle_options/reducer.ts +++ b/x-pack/plugins/infra/public/store/local/waffle_options/reducer.ts @@ -13,18 +13,29 @@ import { InfraNodeType, InfraPathInput, } from '../../../graphql/types'; -import { changeGroupBy, changeMetric, changeNodeType } from './actions'; +import { InfraGroupByOptions } from '../../../lib/lib'; +import { + changeCustomOptions, + changeGroupBy, + changeMetric, + changeNodeType, + changeView, +} from './actions'; export interface WaffleOptionsState { metric: InfraMetricInput; groupBy: InfraPathInput[]; nodeType: InfraNodeType; + view: string; + customOptions: InfraGroupByOptions[]; } export const initialWaffleOptionsState: WaffleOptionsState = { metric: { type: InfraMetricType.cpu }, groupBy: [], nodeType: InfraNodeType.host, + view: 'map', + customOptions: [], }; const currentMetricReducer = reducerWithInitialState(initialWaffleOptionsState.metric).case( @@ -32,6 +43,10 @@ const currentMetricReducer = reducerWithInitialState(initialWaffleOptionsState.m (current, target) => target ); +const currentCustomOptionsReducer = reducerWithInitialState( + initialWaffleOptionsState.customOptions +).case(changeCustomOptions, (current, target) => target); + const currentGroupByReducer = reducerWithInitialState(initialWaffleOptionsState.groupBy).case( changeGroupBy, (current, target) => target @@ -42,8 +57,15 @@ const currentNodeTypeReducer = reducerWithInitialState(initialWaffleOptionsState (current, target) => target ); +const currentViewReducer = reducerWithInitialState(initialWaffleOptionsState.view).case( + changeView, + (current, target) => target +); + export const waffleOptionsReducer = combineReducers({ metric: currentMetricReducer, groupBy: currentGroupByReducer, nodeType: currentNodeTypeReducer, + view: currentViewReducer, + customOptions: currentCustomOptionsReducer, }); diff --git a/x-pack/plugins/infra/public/store/local/waffle_options/selector.ts b/x-pack/plugins/infra/public/store/local/waffle_options/selector.ts index 6889cd6150ab7..d5ee4c2071539 100644 --- a/x-pack/plugins/infra/public/store/local/waffle_options/selector.ts +++ b/x-pack/plugins/infra/public/store/local/waffle_options/selector.ts @@ -8,4 +8,6 @@ import { WaffleOptionsState } from './reducer'; export const selectMetric = (state: WaffleOptionsState) => state.metric; export const selectGroupBy = (state: WaffleOptionsState) => state.groupBy; +export const selectCustomOptions = (state: WaffleOptionsState) => state.customOptions; export const selectNodeType = (state: WaffleOptionsState) => state.nodeType; +export const selectView = (state: WaffleOptionsState) => state.view; diff --git a/x-pack/plugins/infra/public/store/selectors.ts b/x-pack/plugins/infra/public/store/selectors.ts index a604580376ce3..8a2c96cb01039 100644 --- a/x-pack/plugins/infra/public/store/selectors.ts +++ b/x-pack/plugins/infra/public/store/selectors.ts @@ -9,6 +9,7 @@ import { createSelector } from 'reselect'; import { getLogEntryAtTime } from '../utils/log_entry'; import { globalizeSelectors } from '../utils/typed_redux'; import { + flyoutOptionsSelectors as localFlyoutOptionsSelectors, logFilterSelectors as localLogFilterSelectors, logMinimapSelectors as localLogMinimapSelectors, logPositionSelectors as localLogPositionSelectors, @@ -38,6 +39,7 @@ export const metricTimeSelectors = globalizeSelectors(selectLocal, localMetricTi export const waffleFilterSelectors = globalizeSelectors(selectLocal, localWaffleFilterSelectors); export const waffleTimeSelectors = globalizeSelectors(selectLocal, localWaffleTimeSelectors); export const waffleOptionsSelectors = globalizeSelectors(selectLocal, localWaffleOptionsSelectors); +export const flyoutOptionsSelectors = globalizeSelectors(selectLocal, localFlyoutOptionsSelectors); /** * remote selectors diff --git a/x-pack/plugins/infra/server/graphql/log_entries/resolvers.ts b/x-pack/plugins/infra/server/graphql/log_entries/resolvers.ts index 8a0dd691f9978..0c7049c1895a4 100644 --- a/x-pack/plugins/infra/server/graphql/log_entries/resolvers.ts +++ b/x-pack/plugins/infra/server/graphql/log_entries/resolvers.ts @@ -31,6 +31,11 @@ export type InfraSourceLogSummaryBetweenResolver = ChildResolverOf< QuerySourceResolver >; +export type InfraSourceLogItem = ChildResolverOf< + InfraResolverOf, + QuerySourceResolver +>; + export const createLogEntriesResolvers = (libs: { logEntries: InfraLogEntriesDomain; }): { @@ -38,6 +43,7 @@ export const createLogEntriesResolvers = (libs: { logEntriesAround: InfraSourceLogEntriesAroundResolver; logEntriesBetween: InfraSourceLogEntriesBetweenResolver; logSummaryBetween: InfraSourceLogSummaryBetweenResolver; + logItem: InfraSourceLogItem; }; InfraLogMessageSegment: { __resolveType( @@ -115,6 +121,9 @@ export const createLogEntriesResolvers = (libs: { buckets, }; }, + async logItem(source, args, { req }) { + return await libs.logEntries.getLogItem(req, args.id, source.configuration); + }, }, InfraLogMessageSegment: { __resolveType: (messageSegment: InfraLogMessageSegment) => { diff --git a/x-pack/plugins/infra/server/graphql/log_entries/schema.gql.ts b/x-pack/plugins/infra/server/graphql/log_entries/schema.gql.ts index e4947767ffbb1..302b5a374b524 100644 --- a/x-pack/plugins/infra/server/graphql/log_entries/schema.gql.ts +++ b/x-pack/plugins/infra/server/graphql/log_entries/schema.gql.ts @@ -78,6 +78,22 @@ export const logEntriesSchema = gql` buckets: [InfraLogSummaryBucket!]! } + type InfraLogItemField { + "The flattened field name" + field: String! + "The value for the Field as a string" + value: String! + } + + type InfraLogItem { + "The ID of the document" + id: ID! + "The index where the document was found" + index: String! + "An array of flattened fields and values" + fields: [InfraLogItemField!]! + } + extend type InfraSource { "A consecutive span of log entries surrounding a point in time" logEntriesAround( @@ -114,5 +130,6 @@ export const logEntriesSchema = gql` "The query to filter the log entries by" filterQuery: String ): InfraLogSummaryInterval! + logItem(id: ID!): InfraLogItem! } `; diff --git a/x-pack/plugins/infra/server/graphql/nodes/schema.gql.ts b/x-pack/plugins/infra/server/graphql/nodes/schema.gql.ts index b8002963925c3..b1b9b57392054 100644 --- a/x-pack/plugins/infra/server/graphql/nodes/schema.gql.ts +++ b/x-pack/plugins/infra/server/graphql/nodes/schema.gql.ts @@ -10,6 +10,8 @@ export const nodesSchema: any = gql` type InfraNodeMetric { name: InfraMetricType! value: Float! + avg: Float! + max: Float! } type InfraNodePath { @@ -60,6 +62,7 @@ export const nodesSchema: any = gql` hosts pods containers + custom } input InfraPathInput { diff --git a/x-pack/plugins/infra/server/graphql/types.ts b/x-pack/plugins/infra/server/graphql/types.ts index 84c4eb3518fa0..6933202633856 100644 --- a/x-pack/plugins/infra/server/graphql/types.ts +++ b/x-pack/plugins/infra/server/graphql/types.ts @@ -37,7 +37,7 @@ export type SubscriptionResolver { - /** Get an infrastructure data source by id.The resolution order for the source configuration attributes is as followswith the first defined value winning:1. The attributes of the saved object with the given 'id'.2. The attributes defined in the static Kibana configuration key'xpack.infra.sources.default'.3. The hard-coded default values.As a consequence, querying a source without a corresponding saved objectdoesn't error out, but returns the configured or hardcoded defaults. */ + /** Get an infrastructure data source by id.The resolution order for the source configuration attributes is as followswith the first defined value winning:1. The attributes of the saved object with the given 'id'.2. The attributes defined in the static Kibana configuration key'xpack.infra.sources.default'.3. The hard-coded default values.As a consequence, querying a source that doesn't exist doesn't error out,but returns the configured or hardcoded defaults. */ source?: SourceResolver; /** Get a list of all infrastructure data sources */ allSources?: AllSourcesResolver; @@ -595,6 +621,8 @@ export namespace InfraSourceResolvers { logEntriesBetween?: LogEntriesBetweenResolver; /** A consecutive span of summary buckets within an interval */ logSummaryBetween?: LogSummaryBetweenResolver; + + logItem?: LogItemResolver; /** A hierarchy of hosts, pods, containers, services or arbitrary groups */ map?: MapResolver; @@ -687,6 +715,15 @@ export namespace InfraSourceResolvers { filterQuery?: string | null; } + export type LogItemResolver< + R = InfraLogItem, + Parent = InfraSource, + Context = InfraContext + > = Resolver; + export interface LogItemArgs { + id: string; + } + export type MapResolver< R = InfraResponse | null, Parent = InfraSource, @@ -1143,6 +1180,53 @@ export namespace InfraLogSummaryBucketResolvers { > = Resolver; } +export namespace InfraLogItemResolvers { + export interface Resolvers { + /** The ID of the document */ + id?: IdResolver; + /** The index where the document was found */ + index?: IndexResolver; + /** An array of flattened fields and values */ + fields?: FieldsResolver; + } + + export type IdResolver = Resolver< + R, + Parent, + Context + >; + export type IndexResolver = Resolver< + R, + Parent, + Context + >; + export type FieldsResolver< + R = InfraLogItemField[], + Parent = InfraLogItem, + Context = InfraContext + > = Resolver; +} + +export namespace InfraLogItemFieldResolvers { + export interface Resolvers { + /** The flattened field name */ + field?: FieldResolver; + /** The value for the Field as a string */ + value?: ValueResolver; + } + + export type FieldResolver< + R = string, + Parent = InfraLogItemField, + Context = InfraContext + > = Resolver; + export type ValueResolver< + R = string, + Parent = InfraLogItemField, + Context = InfraContext + > = Resolver; +} + export namespace InfraResponseResolvers { export interface Resolvers { nodes?: NodesResolver; @@ -1203,6 +1287,10 @@ export namespace InfraNodeMetricResolvers { name?: NameResolver; value?: ValueResolver; + + avg?: AvgResolver; + + max?: MaxResolver; } export type NameResolver< @@ -1215,6 +1303,16 @@ export namespace InfraNodeMetricResolvers { Parent = InfraNodeMetric, Context = InfraContext > = Resolver; + export type AvgResolver = Resolver< + R, + Parent, + Context + >; + export type MaxResolver = Resolver< + R, + Parent, + Context + >; } export namespace InfraMetricDataResolvers { diff --git a/x-pack/plugins/infra/server/lib/adapters/log_entries/kibana_log_entries_adapter.ts b/x-pack/plugins/infra/server/lib/adapters/log_entries/kibana_log_entries_adapter.ts index f08cfe8f5ca0e..56126c0dd8064 100644 --- a/x-pack/plugins/infra/server/lib/adapters/log_entries/kibana_log_entries_adapter.ts +++ b/x-pack/plugins/infra/server/lib/adapters/log_entries/kibana_log_entries_adapter.ts @@ -5,10 +5,12 @@ */ import { timeMilliseconds } from 'd3-time'; +import first from 'lodash/fp/first'; import get from 'lodash/fp/get'; import has from 'lodash/fp/has'; import zip from 'lodash/fp/zip'; +import { JsonObject } from 'x-pack/plugins/infra/common/typed_json'; import { compareTimeKeys, isTimeKey, TimeKey } from '../../../../common/time'; import { LogEntriesAdapter, @@ -28,6 +30,12 @@ const DAY_MILLIS = 24 * 60 * 60 * 1000; const LOOKUP_OFFSETS = [0, 1, 7, 30, 365, 10000, Infinity].map(days => days * DAY_MILLIS); const TIMESTAMP_FORMAT = 'epoch_millis'; +interface LogItemHit { + _index: string; + _id: string; + _source: JsonObject; +} + export class InfraKibanaLogEntriesAdapter implements LogEntriesAdapter { constructor(private readonly framework: InfraBackendFrameworkAdapter) {} @@ -152,6 +160,35 @@ export class InfraKibanaLogEntriesAdapter implements LogEntriesAdapter { : []; } + public async getLogItem( + request: InfraFrameworkRequest, + id: string, + sourceConfiguration: InfraSourceConfiguration + ) { + const search = (searchOptions: object) => + this.framework.callWithRequest(request, 'search', searchOptions); + + const params = { + index: sourceConfiguration.logAlias, + terminate_after: 1, + body: { + size: 1, + query: { + ids: { + values: [id], + }, + }, + }, + }; + + const response = await search(params); + const document = first(response.hits.hits); + if (!document) { + throw new Error('Document not found'); + } + return document; + } + private async getLogEntryDocumentsBetween( request: InfraFrameworkRequest, sourceConfiguration: InfraSourceConfiguration, diff --git a/x-pack/plugins/infra/server/lib/adapters/nodes/lib/create_node_item.ts b/x-pack/plugins/infra/server/lib/adapters/nodes/lib/create_node_item.ts index 5a5bdabdfdedc..7c02b2d61b3ec 100644 --- a/x-pack/plugins/infra/server/lib/adapters/nodes/lib/create_node_item.ts +++ b/x-pack/plugins/infra/server/lib/adapters/nodes/lib/create_node_item.ts @@ -4,11 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { get, last } from 'lodash'; -import { isNumber } from 'lodash'; +import { get, isNumber, last, max, sum } from 'lodash'; import moment from 'moment'; -import { InfraNode, InfraNodeMetric } from '../../../../graphql/types'; +import { InfraMetricType, InfraNode, InfraNodeMetric } from '../../../../graphql/types'; import { InfraBucket, InfraNodeRequestOptions } from '../adapter_types'; import { NAME_FIELDS } from '../constants'; import { getBucketSizeInSeconds } from './get_bucket_size_in_seconds'; @@ -34,6 +33,21 @@ const findLastFullBucket = ( }, last(buckets)); }; +const getMetricValueFromBucket = (type: InfraMetricType) => (bucket: InfraBucket) => { + const metric = bucket[type]; + return (metric && (metric.normalized_value || metric.value)) || 0; +}; + +function calculateMax(bucket: InfraBucket, type: InfraMetricType) { + const { buckets } = bucket.timeseries; + return max(buckets.map(getMetricValueFromBucket(type))) || 0; +} + +function calculateAvg(bucket: InfraBucket, type: InfraMetricType) { + const { buckets } = bucket.timeseries; + return sum(buckets.map(getMetricValueFromBucket(type))) / buckets.length || 0; +} + function createNodeMetrics( options: InfraNodeRequestOptions, node: InfraBucket, @@ -45,11 +59,11 @@ function createNodeMetrics( if (!lastBucket) { throw new Error('Date histogram returned an empty set of buckets.'); } - const metricObj = lastBucket[metric.type]; - const value = (metricObj && (metricObj.normalized_value || metricObj.value)) || 0; return { name: metric.type, - value, + value: getMetricValueFromBucket(metric.type)(lastBucket), + max: calculateMax(bucket, metric.type), + avg: calculateAvg(bucket, metric.type), }; } diff --git a/x-pack/plugins/infra/server/lib/adapters/nodes/lib/extract_group_paths.ts b/x-pack/plugins/infra/server/lib/adapters/nodes/lib/extract_group_paths.ts index 1ea42536b86a9..7e1a7857ced34 100644 --- a/x-pack/plugins/infra/server/lib/adapters/nodes/lib/extract_group_paths.ts +++ b/x-pack/plugins/infra/server/lib/adapters/nodes/lib/extract_group_paths.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { InfraNode, InfraPathInput } from '../../../../graphql/types'; +import { InfraNode, InfraNodePath, InfraPathInput } from '../../../../graphql/types'; import { InfraBucket, InfraNodeRequestOptions } from '../adapter_types'; import { createNodeItem } from './create_node_item'; @@ -27,10 +27,11 @@ export function extractGroupPaths( bucket.path_1.buckets.map( (b: InfraBucket): InfraNode => { const innerNode = createNodeItem(options, node, b); - const nodePath = [ + const groupPaths: InfraNodePath[] = [ { value: bucket.key.toString(), label: bucket.key.toString() }, { value: b.key.toString(), label: b.key.toString() }, - ].concat(innerNode.path); + ]; + const nodePath = groupPaths.concat(innerNode.path); return { ...innerNode, path: nodePath, @@ -40,7 +41,8 @@ export function extractGroupPaths( ); } const nodeItem = createNodeItem(options, node, bucket); - const path = [{ value: key, label: key }].concat(nodeItem.path); + const currentPath: InfraNodePath[] = [{ value: key, label: key }]; + const path = currentPath.concat(nodeItem.path); return acc.concat({ ...nodeItem, path, diff --git a/x-pack/plugins/infra/server/lib/adapters/nodes/processors/last/date_histogram_processor.ts b/x-pack/plugins/infra/server/lib/adapters/nodes/processors/last/date_histogram_processor.ts index 31ef88ed3229c..df63cf4234645 100644 --- a/x-pack/plugins/infra/server/lib/adapters/nodes/processors/last/date_histogram_processor.ts +++ b/x-pack/plugins/infra/server/lib/adapters/nodes/processors/last/date_histogram_processor.ts @@ -5,7 +5,6 @@ */ import { cloneDeep, set } from 'lodash'; -import moment from 'moment'; import { InfraESSearchBody, InfraProcesorRequestOptions } from '../../adapter_types'; import { createBasePath } from '../../lib/create_base_path'; import { getBucketSizeInSeconds } from '../../lib/get_bucket_size_in_seconds'; @@ -24,10 +23,6 @@ export const dateHistogramProcessor = (options: InfraProcesorRequestOptions) => const result = cloneDeep(doc); const { timerange, sourceConfiguration, groupBy } = options.nodeOptions; const bucketSizeInSeconds = getBucketSizeInSeconds(timerange.interval); - const boundsMin = moment - .utc(timerange.from) - .subtract(5 * bucketSizeInSeconds, 's') - .valueOf(); const path = createBasePath(groupBy).concat('timeseries'); const bucketOffset = calculateOffsetInSeconds(timerange.from, bucketSizeInSeconds); const offset = `${Math.floor(bucketOffset)}s`; @@ -38,7 +33,7 @@ export const dateHistogramProcessor = (options: InfraProcesorRequestOptions) => min_doc_count: 0, offset, extended_bounds: { - min: boundsMin, + min: timerange.from, max: timerange.to, }, }, diff --git a/x-pack/plugins/infra/server/lib/domains/log_entries_domain/convert_document_source_to_log_item_fields.test.ts b/x-pack/plugins/infra/server/lib/domains/log_entries_domain/convert_document_source_to_log_item_fields.test.ts new file mode 100644 index 0000000000000..d46ebbb26fbb8 --- /dev/null +++ b/x-pack/plugins/infra/server/lib/domains/log_entries_domain/convert_document_source_to_log_item_fields.test.ts @@ -0,0 +1,67 @@ +/* + * 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 { convertDocumentSourceToLogItemFields } from './convert_document_source_to_log_item_fields'; + +describe('convertDocumentSourceToLogItemFields', () => { + test('should convert document', () => { + const doc = { + agent: { + hostname: 'demo-stack-client-01', + id: '7adef8b6-2ab7-45cd-a0d5-b3baad735f1b', + type: 'filebeat', + ephemeral_id: 'a0c8164b-3564-4e32-b0bf-f4db5a7ae566', + version: '7.0.0', + }, + tags: ['prod', 'web'], + metadata: [{ key: 'env', value: 'prod' }, { key: 'stack', value: 'web' }], + host: { + hostname: 'packer-virtualbox-iso-1546820004', + name: 'demo-stack-client-01', + }, + }; + + const fields = convertDocumentSourceToLogItemFields(doc); + expect(fields).toEqual([ + { + field: 'agent.hostname', + value: 'demo-stack-client-01', + }, + { + field: 'agent.id', + value: '7adef8b6-2ab7-45cd-a0d5-b3baad735f1b', + }, + { + field: 'agent.type', + value: 'filebeat', + }, + { + field: 'agent.ephemeral_id', + value: 'a0c8164b-3564-4e32-b0bf-f4db5a7ae566', + }, + { + field: 'agent.version', + value: '7.0.0', + }, + { + field: 'tags', + value: '["prod","web"]', + }, + { + field: 'metadata', + value: '[{"key":"env","value":"prod"},{"key":"stack","value":"web"}]', + }, + { + field: 'host.hostname', + value: 'packer-virtualbox-iso-1546820004', + }, + { + field: 'host.name', + value: 'demo-stack-client-01', + }, + ]); + }); +}); diff --git a/x-pack/plugins/infra/server/lib/domains/log_entries_domain/convert_document_source_to_log_item_fields.ts b/x-pack/plugins/infra/server/lib/domains/log_entries_domain/convert_document_source_to_log_item_fields.ts new file mode 100644 index 0000000000000..923c0c22211fe --- /dev/null +++ b/x-pack/plugins/infra/server/lib/domains/log_entries_domain/convert_document_source_to_log_item_fields.ts @@ -0,0 +1,36 @@ +/* + * 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 { isArray, isPlainObject } from 'lodash'; +import { JsonObject } from 'x-pack/plugins/infra/common/typed_json'; +import { InfraLogItemField } from '../../../graphql/types'; + +const isJsonObject = (subject: any): subject is JsonObject => { + return isPlainObject(subject); +}; + +const serializeValue = (value: any): string => { + if (isArray(value) || isPlainObject(value)) { + return JSON.stringify(value); + } + return `${value}`; +}; + +export const convertDocumentSourceToLogItemFields = ( + source: JsonObject, + path: string[] = [], + fields: InfraLogItemField[] = [] +): InfraLogItemField[] => { + return Object.keys(source).reduce((acc, key) => { + const value = source[key]; + const nextPath = [...path, key]; + if (isJsonObject(value)) { + return convertDocumentSourceToLogItemFields(value, nextPath, acc); + } + const field = { field: nextPath.join('.'), value: serializeValue(value) }; + return [...acc, field]; + }, fields); +}; diff --git a/x-pack/plugins/infra/server/lib/domains/log_entries_domain/log_entries_domain.ts b/x-pack/plugins/infra/server/lib/domains/log_entries_domain/log_entries_domain.ts index 5781ca3ad8ef8..e605efc105617 100644 --- a/x-pack/plugins/infra/server/lib/domains/log_entries_domain/log_entries_domain.ts +++ b/x-pack/plugins/infra/server/lib/domains/log_entries_domain/log_entries_domain.ts @@ -4,8 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ +import { sortBy } from 'lodash'; import { TimeKey } from '../../../../common/time'; import { JsonObject } from '../../../../common/typed_json'; +import { InfraLogItem } from '../../../graphql/types'; import { InfraLogEntry, InfraLogMessageSegment, @@ -14,6 +16,7 @@ import { import { InfraDateRangeAggregationBucket, InfraFrameworkRequest } from '../../adapters/framework'; import { InfraSourceConfiguration, InfraSources } from '../../sources'; import { builtinRules } from './builtin_rules'; +import { convertDocumentSourceToLogItemFields } from './convert_document_source_to_log_item_fields'; import { compileFormattingRules } from './message'; export class InfraLogEntriesDomain { @@ -121,6 +124,32 @@ export class InfraLogEntriesDomain { const buckets = dateRangeBuckets.map(convertDateRangeBucketToSummaryBucket); return buckets; } + + public async getLogItem( + request: InfraFrameworkRequest, + id: string, + sourceConfiguration: InfraSourceConfiguration + ): Promise { + const document = await this.adapter.getLogItem(request, id, sourceConfiguration); + const defaultFields = [ + { field: '_index', value: document._index }, + { field: '_id', value: document._id }, + ]; + return { + id: document._id, + index: document._index, + fields: sortBy( + [...defaultFields, ...convertDocumentSourceToLogItemFields(document._source)], + 'field' + ), + }; + } +} + +interface LogItemHit { + _index: string; + _id: string; + _source: JsonObject; } export interface LogEntriesAdapter { @@ -153,6 +182,12 @@ export interface LogEntriesAdapter { bucketSize: number, filterQuery?: LogEntryQuery ): Promise; + + getLogItem( + request: InfraFrameworkRequest, + id: string, + source: InfraSourceConfiguration + ): Promise; } export type LogEntryQuery = JsonObject; diff --git a/x-pack/plugins/infra/tsconfig.json b/x-pack/plugins/infra/tsconfig.json index 4082f16a5d91c..01bddbfc4264d 100644 --- a/x-pack/plugins/infra/tsconfig.json +++ b/x-pack/plugins/infra/tsconfig.json @@ -1,3 +1,3 @@ { - "extends": "../../tsconfig.json" -} + "extends": "../../tsconfig.json", +} \ No newline at end of file diff --git a/x-pack/plugins/infra/types/eui.d.ts b/x-pack/plugins/infra/types/eui.d.ts index c22337f16feda..78a9f81e5e6fd 100644 --- a/x-pack/plugins/infra/types/eui.d.ts +++ b/x-pack/plugins/infra/types/eui.d.ts @@ -170,4 +170,34 @@ declare module '@elastic/eui' { }; export const EuiDatePickerRange: React.SFC; + + type EuiInMemoryTableProps = CommonProps & { + items?: any; + columns?: any; + sorting?: any; + search?: any; + selection?: any; + pagination?: any; + itemId?: any; + isSelectable?: any; + loading?: any; + hasActions?: any; + message?: any; + }; + export const EuiInMemoryTable: React.SFC; + + type EuiButtonGroupProps = CommonProps & { + buttonSize?: any; + color?: any; + idToSelectedMap?: any; + options?: any; + type?: any; + onChange?: any; + isIconOnly?: any; + isDisabled?: any; + isFullWidth?: any; + legend?: any; + idSelected?: any; + }; + export const EuiButtonGroup: React.SFC; } diff --git a/x-pack/plugins/ml/public/explorer/explorer_charts/__mocks__/mock_chart_data.js b/x-pack/plugins/ml/public/explorer/explorer_charts/__mocks__/mock_chart_data.js index e150335114a20..82f8b8d30580f 100644 --- a/x-pack/plugins/ml/public/explorer/explorer_charts/__mocks__/mock_chart_data.js +++ b/x-pack/plugins/ml/public/explorer/explorer_charts/__mocks__/mock_chart_data.js @@ -22,5 +22,10 @@ export const chartData = [ date: new Date('2017-02-23T13:00:00.000Z'), value: 201039318, anomalyScore: 59.83488, numberOfCauses: 1, actual: [201039318], typical: [132739.5267403542] + }, + { + date: new Date('2017-02-23T14:00:00.000Z'), + value: null, anomalyScore: 98.56166, + actual: [201039318], typical: [132739.5267403542] } ]; diff --git a/x-pack/plugins/ml/public/explorer/explorer_charts/explorer_chart_single_metric.js b/x-pack/plugins/ml/public/explorer/explorer_charts/explorer_chart_single_metric.js index a1d79be061edc..f849271f91c2d 100644 --- a/x-pack/plugins/ml/public/explorer/explorer_charts/explorer_chart_single_metric.js +++ b/x-pack/plugins/ml/public/explorer/explorer_charts/explorer_chart_single_metric.js @@ -267,11 +267,13 @@ export const ExplorerChartSingleMetric = injectI18n(class ExplorerChartSingleMet function drawLineChartMarkers(data) { // Render circle markers for the points. // These are used for displaying tooltips on mouseover. - // Don't render dots where value=null (data gaps) or for multi-bucket anomalies. + // Don't render dots where value=null (data gaps, with no anomalies) + // or for multi-bucket anomalies. const dots = lineChartGroup.append('g') .attr('class', 'chart-markers') .selectAll('.metric-value') - .data(data.filter(d => (d.value !== null && !showMultiBucketAnomalyMarker(d)))); + .data(data.filter(d => ((d.value !== null || typeof d.anomalyScore === 'number') && + !showMultiBucketAnomalyMarker(d)))); // Remove dots that are no longer needed i.e. if number of chart points has decreased. dots.exit().remove(); diff --git a/x-pack/plugins/ml/public/explorer/explorer_charts/explorer_chart_single_metric.test.js b/x-pack/plugins/ml/public/explorer/explorer_charts/explorer_chart_single_metric.test.js index ad8a04e1af75d..3398eb2e168ec 100644 --- a/x-pack/plugins/ml/public/explorer/explorer_charts/explorer_chart_single_metric.test.js +++ b/x-pack/plugins/ml/public/explorer/explorer_charts/explorer_chart_single_metric.test.js @@ -135,8 +135,8 @@ describe('ExplorerChart', () => { expect(dots[0].getAttribute('r')).toBe('1.5'); const chartMarkers = wrapper.getDOMNode().querySelector('.chart-markers').querySelectorAll('circle'); - expect([...chartMarkers]).toHaveLength(3); - expect([...chartMarkers].map(d => +d.getAttribute('r'))).toEqual([7, 7, 7]); + expect([...chartMarkers]).toHaveLength(4); + expect([...chartMarkers].map(d => +d.getAttribute('r'))).toEqual([7, 7, 7, 7]); }); it('Anomaly Explorer Chart with single data point', () => { diff --git a/x-pack/plugins/ml/public/jobs/new_job/simple/recognize/create_job/create_job.html b/x-pack/plugins/ml/public/jobs/new_job/simple/recognize/create_job/create_job.html index 608adad54fb43..c97d5dde13117 100644 --- a/x-pack/plugins/ml/public/jobs/new_job/simple/recognize/create_job/create_job.html +++ b/x-pack/plugins/ml/public/jobs/new_job/simple/recognize/create_job/create_job.html @@ -340,6 +340,7 @@

{{ui.kibanaLabels[key]}}

i18n-default-message="(already exists)" >
+
{{error}}
diff --git a/x-pack/plugins/ml/public/jobs/new_job/simple/recognize/create_job/create_job_controller.js b/x-pack/plugins/ml/public/jobs/new_job/simple/recognize/create_job/create_job_controller.js index a7899729d8df7..0e518e38f8eca 100644 --- a/x-pack/plugins/ml/public/jobs/new_job/simple/recognize/create_job/create_job_controller.js +++ b/x-pack/plugins/ml/public/jobs/new_job/simple/recognize/create_job/create_job_controller.js @@ -212,7 +212,8 @@ module title: o.title, saveState: SAVE_STATE.NOT_SAVED, config: o.config, - exists: false + exists: false, + errors: [], }; }); }); @@ -342,6 +343,9 @@ module obj.saveState = SAVE_STATE.SAVED; } else { obj.saveState = SAVE_STATE.FAILED; + if (kibanaObjectResult.error && kibanaObjectResult.error.message) { + obj.errors.push(kibanaObjectResult.error.message); + } } } else { obj.saveState = SAVE_STATE.FAILED; diff --git a/x-pack/plugins/ml/public/jobs/new_job/wizard/steps/job_type/job_type_controller.js b/x-pack/plugins/ml/public/jobs/new_job/wizard/steps/job_type/job_type_controller.js index 56d8b9b2567ba..970107e474b8c 100644 --- a/x-pack/plugins/ml/public/jobs/new_job/wizard/steps/job_type/job_type_controller.js +++ b/x-pack/plugins/ml/public/jobs/new_job/wizard/steps/job_type/job_type_controller.js @@ -74,7 +74,12 @@ module.controller('MlNewJobStepJobType', $scope.indexPattern = indexPattern; $scope.savedSearch = savedSearch; - $scope.recognizerResults = { count: 0 }; + $scope.recognizerResults = { + count: 0, + onChange() { + $scope.$applyAsync(); + } + }; $scope.pageTitleLabel = (savedSearch.id !== undefined) ? i18n('xpack.ml.newJob.wizard.jobType.savedSearchPageTitleLabel', { diff --git a/x-pack/plugins/ml/public/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js b/x-pack/plugins/ml/public/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js index 91129acf772d0..ee08c9376c8dc 100644 --- a/x-pack/plugins/ml/public/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js +++ b/x-pack/plugins/ml/public/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js @@ -677,10 +677,20 @@ export const TimeseriesChart = injectI18n(class TimeseriesChart extends React.Co } yMin = d3.min(combinedData, (d) => { - return d.lower !== undefined ? Math.min(d.value, d.lower) : d.value; + let metricValue = d.value; + if (metricValue === null && d.anomalyScore !== undefined && d.actual !== undefined) { + // If an anomaly coincides with a gap in the data, use the anomaly actual value. + metricValue = Array.isArray(d.actual) ? d.actual[0] : d.actual; + } + return d.lower !== undefined ? Math.min(metricValue, d.lower) : metricValue; }); yMax = d3.max(combinedData, (d) => { - return d.upper !== undefined ? Math.max(d.value, d.upper) : d.value; + let metricValue = d.value; + if (metricValue === null && d.anomalyScore !== undefined && d.actual !== undefined) { + // If an anomaly coincides with a gap in the data, use the anomaly actual value. + metricValue = Array.isArray(d.actual) ? d.actual[0] : d.actual; + } + return d.upper !== undefined ? Math.max(metricValue, d.upper) : metricValue; }); if (yMax === yMin) { @@ -702,9 +712,10 @@ export const TimeseriesChart = injectI18n(class TimeseriesChart extends React.Co if (mlAnnotationsEnabled && focusAnnotationData && focusAnnotationData.length > 0) { const levels = getAnnotationLevels(focusAnnotationData); const maxLevel = d3.max(Object.keys(levels).map(key => levels[key])); - // TODO needs revisting to be a more robust normalization + // TODO needs revisiting to be a more robust normalization yMax = yMax * (1 + (maxLevel + 1) / 5); } + this.focusYScale.domain([yMin, yMax]); } else { @@ -758,9 +769,11 @@ export const TimeseriesChart = injectI18n(class TimeseriesChart extends React.Co // Render circle markers for the points. // These are used for displaying tooltips on mouseover. - // Don't render dots where value=null (data gaps) or for multi-bucket anomalies. + // Don't render dots where value=null (data gaps, with no anomalies) + // or for multi-bucket anomalies. const dots = d3.select('.focus-chart-markers').selectAll('.metric-value') - .data(data.filter(d => (d.value !== null && !showMultiBucketAnomalyMarker(d)))); + .data(data.filter(d => ((d.value !== null || typeof d.anomalyScore === 'number') && + !showMultiBucketAnomalyMarker(d)))); // Remove dots that are no longer needed i.e. if number of chart points has decreased. dots.exit().remove(); diff --git a/x-pack/plugins/ml/public/util/__tests__/chart_utils.js b/x-pack/plugins/ml/public/util/__tests__/chart_utils.js index 75d575889eaec..979615f2778be 100644 --- a/x-pack/plugins/ml/public/util/__tests__/chart_utils.js +++ b/x-pack/plugins/ml/public/util/__tests__/chart_utils.js @@ -71,6 +71,26 @@ describe('ML - chart utils', () => { expect(limits.max).to.be(105); }); + it('returns minimum of 0 when data includes an anomaly for missing data', () => { + const data = [ + { date: new Date('2017-02-23T09:00:00.000Z'), value: 22.2 }, + { date: new Date('2017-02-23T10:00:00.000Z'), value: 23.3 }, + { date: new Date('2017-02-23T11:00:00.000Z'), value: 24.4 }, + { + date: new Date('2017-02-23T12:00:00.000Z'), + value: null, anomalyScore: 97.32085, + actual: [0], typical: [22.2] + }, + { date: new Date('2017-02-23T13:00:00.000Z'), value: 21.3 }, + { date: new Date('2017-02-23T14:00:00.000Z'), value: 21.2 }, + { date: new Date('2017-02-23T15:00:00.000Z'), value: 21.1 } + ]; + + const limits = chartLimits(data); + expect(limits.min).to.be(0); + expect(limits.max).to.be(24.4); + }); + }); describe('filterAxisLabels', () => { diff --git a/x-pack/plugins/ml/public/util/chart_utils.js b/x-pack/plugins/ml/public/util/chart_utils.js index 0a061f32fbf6f..0163116bd3367 100644 --- a/x-pack/plugins/ml/public/util/chart_utils.js +++ b/x-pack/plugins/ml/public/util/chart_utils.js @@ -24,10 +24,16 @@ export const SCHEDULED_EVENT_SYMBOL_HEIGHT = 5; const MAX_LABEL_WIDTH = 100; export function chartLimits(data = []) { - const limits = { max: 0, min: 0 }; + const domain = d3.extent(data, (d) => { + let metricValue = d.value; + if (metricValue === null && d.anomalyScore !== undefined && d.actual !== undefined) { + // If an anomaly coincides with a gap in the data, use the anomaly actual value. + metricValue = Array.isArray(d.actual) ? d.actual[0] : d.actual; + } + return metricValue; + }); + const limits = { max: domain[1], min: domain[0] }; - limits.max = d3.max(data, (d) => d.value); - limits.min = d3.min(data, (d) => d.value); if (limits.max === limits.min) { limits.max = d3.max(data, (d) => { if (d.typical) { diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/apache2/kibana/dashboard/ML-Apache2-Access-Remote-IP-Count-Explorer.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache2/kibana/dashboard/ML-Apache2-Access-Remote-IP-Count-Explorer.json index 5fc696c6c702d..24b87e39b35db 100644 --- a/x-pack/plugins/ml/server/models/data_recognizer/modules/apache2/kibana/dashboard/ML-Apache2-Access-Remote-IP-Count-Explorer.json +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache2/kibana/dashboard/ML-Apache2-Access-Remote-IP-Count-Explorer.json @@ -7,6 +7,7 @@ "panelsJSON": "[{\"size_x\":6,\"size_y\":3,\"panelIndex\":1,\"type\":\"visualization\",\"id\":\"ML-Apache2-Access-Remote-IP-Timechart\",\"col\":1,\"row\":1},{\"size_x\":6,\"size_y\":3,\"panelIndex\":2,\"type\":\"visualization\",\"id\":\"ML-Apache2-Access-Response-Code-Timechart\",\"col\":7,\"row\":1},{\"size_x\":6,\"size_y\":3,\"panelIndex\":3,\"type\":\"visualization\",\"id\":\"ML-Apache2-Access-Top-Remote-IPs-Table\",\"col\":1,\"row\":4},{\"size_x\":6,\"size_y\":3,\"panelIndex\":4,\"type\":\"visualization\",\"id\":\"ML-Apache2-Access-Map\",\"col\":7,\"row\":4},{\"size_x\":12,\"size_y\":9,\"panelIndex\":5,\"type\":\"visualization\",\"id\":\"ML-Apache2-Access-Top-URLs-Table\",\"col\":1,\"row\":7}]", "optionsJSON": "{}", "version": 1, + "migrationVersion": {}, "kibanaSavedObjectMeta": { "searchSourceJSON": "{\"filter\":[{\"query\":{\"query_string\":{\"analyze_wildcard\":true,\"query\":\"*\"}}}],\"highlightAll\":true,\"version\":true}" } diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/apache2/kibana/dashboard/ML-Apache2-Remote-IP-URL-Explorer.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache2/kibana/dashboard/ML-Apache2-Remote-IP-URL-Explorer.json index b04050ceb6e19..d4ef153201bf2 100644 --- a/x-pack/plugins/ml/server/models/data_recognizer/modules/apache2/kibana/dashboard/ML-Apache2-Remote-IP-URL-Explorer.json +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache2/kibana/dashboard/ML-Apache2-Remote-IP-URL-Explorer.json @@ -7,6 +7,7 @@ "panelsJSON": "[{\"col\":1,\"id\":\"ML-Apache2-Access-Unique-Count-URL-Timechart\",\"panelIndex\":1,\"row\":1,\"size_x\":6,\"size_y\":3,\"type\":\"visualization\"},{\"col\":7,\"id\":\"ML-Apache2-Access-Response-Code-Timechart\",\"panelIndex\":2,\"row\":1,\"size_x\":6,\"size_y\":3,\"type\":\"visualization\"},{\"col\":1,\"id\":\"ML-Apache2-Access-Top-Remote-IPs-Table\",\"panelIndex\":3,\"row\":4,\"size_x\":6,\"size_y\":3,\"type\":\"visualization\"},{\"col\":7,\"id\":\"ML-Apache2-Access-Map\",\"panelIndex\":4,\"row\":4,\"size_x\":6,\"size_y\":3,\"type\":\"visualization\"},{\"size_x\":12,\"size_y\":8,\"panelIndex\":5,\"type\":\"visualization\",\"id\":\"ML-Apache2-Access-Top-URLs-Table\",\"col\":1,\"row\":7}]", "optionsJSON": "{}", "version": 1, + "migrationVersion": {}, "kibanaSavedObjectMeta": { "searchSourceJSON": "{\"filter\":[{\"query\":{\"query_string\":{\"analyze_wildcard\":true,\"query\":\"*\"}}}],\"highlightAll\":true,\"version\":true}" } diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/apache2/kibana/search/ML-Filebeat-Apache2-Access.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache2/kibana/search/ML-Filebeat-Apache2-Access.json index edb54752c2ffe..7c6124295aae3 100644 --- a/x-pack/plugins/ml/server/models/data_recognizer/modules/apache2/kibana/search/ML-Filebeat-Apache2-Access.json +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache2/kibana/search/ML-Filebeat-Apache2-Access.json @@ -7,6 +7,7 @@ "description": "Filebeat Apache2 Access Data", "title": "ML Apache2 Access Data", "version": 1, + "migrationVersion": {}, "kibanaSavedObjectMeta": { "searchSourceJSON": "{\"index\":\"INDEX_PATTERN_ID\",\"query\":{\"query_string\":{\"query\":\"_exists_:apache2.access\",\"analyze_wildcard\":true}},\"filter\":[],\"highlight\":{\"pre_tags\":[\"@kibana-highlighted-field@\"],\"post_tags\":[\"@/kibana-highlighted-field@\"],\"fields\":{\"*\":{}},\"require_field_match\":false,\"fragment_size\":2147483647}}" }, diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/apache2/kibana/visualization/ML-Apache2-Access-Map.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache2/kibana/visualization/ML-Apache2-Access-Map.json index f782f329c037b..c2df807f18985 100644 --- a/x-pack/plugins/ml/server/models/data_recognizer/modules/apache2/kibana/visualization/ML-Apache2-Access-Map.json +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache2/kibana/visualization/ML-Apache2-Access-Map.json @@ -1,11 +1,12 @@ { - "visState": "{\"aggs\":[{\"enabled\":true,\"id\":\"1\",\"params\":{},\"schema\":\"metric\",\"type\":\"count\"},{\"enabled\":true,\"id\":\"2\",\"params\":{\"autoPrecision\":true,\"field\":\"apache2.access.geoip.location\"},\"schema\":\"segment\",\"type\":\"geohash_grid\"}],\"listeners\":{},\"params\":{\"addTooltip\":true,\"heatBlur\":15,\"heatMaxZoom\":16,\"heatMinOpacity\":0.1,\"heatNormalizeData\":true,\"heatRadius\":25,\"isDesaturated\":true,\"legendPosition\":\"bottomright\",\"mapCenter\":[15,5],\"mapType\":\"Scaled Circle Markers\",\"mapZoom\":2,\"wms\":{\"enabled\":false,\"options\":{\"attribution\":\"Maps provided by USGS\",\"format\":\"image/png\",\"layers\":\"0\",\"styles\":\"\",\"transparent\":true,\"version\":\"1.3.0\"},\"url\":\"https://basemap.nationalmap.gov/arcgis/services/USGSTopo/MapServer/WMSServer\"}},\"title\":\"ML Apache2 Access Map\",\"type\":\"tile_map\"}", - "description": "", - "title": "ML Apache2 Access Map", - "uiStateJSON": "{\n \"mapCenter\": [\n 12.039320557540572,\n -0.17578125\n ]\n}", - "version": 1, - "savedSearchId": "ML-Filebeat-Apache2-Access", + "visState": "{\"aggs\":[{\"enabled\":true,\"id\":\"1\",\"params\":{},\"schema\":\"metric\",\"type\":\"count\"},{\"enabled\":true,\"id\":\"2\",\"params\":{\"autoPrecision\":true,\"field\":\"apache2.access.geoip.location\"},\"schema\":\"segment\",\"type\":\"geohash_grid\"}],\"listeners\":{},\"params\":{\"addTooltip\":true,\"heatBlur\":15,\"heatMaxZoom\":16,\"heatMinOpacity\":0.1,\"heatNormalizeData\":true,\"heatRadius\":25,\"isDesaturated\":true,\"legendPosition\":\"bottomright\",\"mapCenter\":[15,5],\"mapType\":\"Scaled Circle Markers\",\"mapZoom\":2,\"wms\":{\"enabled\":false,\"options\":{\"attribution\":\"Maps provided by USGS\",\"format\":\"image/png\",\"layers\":\"0\",\"styles\":\"\",\"transparent\":true,\"version\":\"1.3.0\"},\"url\":\"https://basemap.nationalmap.gov/arcgis/services/USGSTopo/MapServer/WMSServer\"}},\"title\":\"ML Apache2 Access Map\",\"type\":\"tile_map\"}", + "description": "", + "title": "ML Apache2 Access Map", + "uiStateJSON": "{\n \"mapCenter\": [\n 12.039320557540572,\n -0.17578125\n ]\n}", + "version": 1, + "migrationVersion": {}, + "savedSearchId": "ML-Filebeat-Apache2-Access", "kibanaSavedObjectMeta": { "searchSourceJSON": "{\"filter\":[]}" } -} \ No newline at end of file +} diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/apache2/kibana/visualization/ML-Apache2-Access-Remote-IP-Timechart.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache2/kibana/visualization/ML-Apache2-Access-Remote-IP-Timechart.json index dcceff40a2c0f..0789ee1e03f6f 100644 --- a/x-pack/plugins/ml/server/models/data_recognizer/modules/apache2/kibana/visualization/ML-Apache2-Access-Remote-IP-Timechart.json +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache2/kibana/visualization/ML-Apache2-Access-Remote-IP-Timechart.json @@ -1,11 +1,12 @@ { - "visState": "{\"title\":\"ML Apache2 Access Remote IP Timechart\",\"type\":\"area\",\"params\":{\"addLegend\":true,\"addTimeMarker\":false,\"addTooltip\":true,\"categoryAxes\":[{\"id\":\"CategoryAxis-1\",\"labels\":{\"show\":true,\"truncate\":100},\"position\":\"bottom\",\"scale\":{\"type\":\"linear\"},\"show\":true,\"style\":{},\"title\":{\"text\":\"@timestamp per 5 minutes\"},\"type\":\"category\"}],\"defaultYExtents\":false,\"drawLinesBetweenPoints\":true,\"grid\":{\"categoryLines\":false,\"style\":{\"color\":\"#eee\"}},\"interpolate\":\"linear\",\"legendPosition\":\"right\",\"radiusRatio\":9,\"scale\":\"linear\",\"seriesParams\":[{\"data\":{\"id\":\"1\",\"label\":\"Count\"},\"drawLinesBetweenPoints\":true,\"interpolate\":\"linear\",\"mode\":\"stacked\",\"show\":\"true\",\"showCircles\":true,\"type\":\"area\",\"valueAxis\":\"ValueAxis-1\"}],\"setYExtents\":false,\"showCircles\":true,\"times\":[],\"valueAxes\":[{\"id\":\"ValueAxis-1\",\"labels\":{\"filter\":false,\"rotate\":0,\"show\":true,\"truncate\":100},\"name\":\"LeftAxis-1\",\"position\":\"left\",\"scale\":{\"mode\":\"normal\",\"type\":\"linear\"},\"show\":true,\"style\":{},\"title\":{},\"type\":\"value\"}]},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"date_histogram\",\"schema\":\"segment\",\"params\":{\"field\":\"@timestamp\",\"interval\":\"auto\",\"customInterval\":\"2h\",\"min_doc_count\":1,\"extended_bounds\":{}}},{\"id\":\"3\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"group\",\"params\":{\"field\":\"apache2.access.remote_ip\",\"size\":5,\"order\":\"desc\",\"orderBy\":\"1\"}}],\"listeners\":{}}", - "description": "", - "title": "ML Apache2 Access Remote IP Timechart", - "uiStateJSON": "{\"vis\":{\"legendOpen\":false}}", - "version": 1, - "savedSearchId": "ML-Filebeat-Apache2-Access", + "visState": "{\"title\":\"ML Apache2 Access Remote IP Timechart\",\"type\":\"area\",\"params\":{\"addLegend\":true,\"addTimeMarker\":false,\"addTooltip\":true,\"categoryAxes\":[{\"id\":\"CategoryAxis-1\",\"labels\":{\"show\":true,\"truncate\":100},\"position\":\"bottom\",\"scale\":{\"type\":\"linear\"},\"show\":true,\"style\":{},\"title\":{\"text\":\"@timestamp per 5 minutes\"},\"type\":\"category\"}],\"defaultYExtents\":false,\"drawLinesBetweenPoints\":true,\"grid\":{\"categoryLines\":false,\"style\":{\"color\":\"#eee\"}},\"interpolate\":\"linear\",\"legendPosition\":\"right\",\"radiusRatio\":9,\"scale\":\"linear\",\"seriesParams\":[{\"data\":{\"id\":\"1\",\"label\":\"Count\"},\"drawLinesBetweenPoints\":true,\"interpolate\":\"linear\",\"mode\":\"stacked\",\"show\":\"true\",\"showCircles\":true,\"type\":\"area\",\"valueAxis\":\"ValueAxis-1\"}],\"setYExtents\":false,\"showCircles\":true,\"times\":[],\"valueAxes\":[{\"id\":\"ValueAxis-1\",\"labels\":{\"filter\":false,\"rotate\":0,\"show\":true,\"truncate\":100},\"name\":\"LeftAxis-1\",\"position\":\"left\",\"scale\":{\"mode\":\"normal\",\"type\":\"linear\"},\"show\":true,\"style\":{},\"title\":{},\"type\":\"value\"}]},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"date_histogram\",\"schema\":\"segment\",\"params\":{\"field\":\"@timestamp\",\"interval\":\"auto\",\"customInterval\":\"2h\",\"min_doc_count\":1,\"extended_bounds\":{}}},{\"id\":\"3\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"group\",\"params\":{\"field\":\"apache2.access.remote_ip\",\"size\":5,\"order\":\"desc\",\"orderBy\":\"1\"}}],\"listeners\":{}}", + "description": "", + "title": "ML Apache2 Access Remote IP Timechart", + "uiStateJSON": "{\"vis\":{\"legendOpen\":false}}", + "version": 1, + "migrationVersion": {}, + "savedSearchId": "ML-Filebeat-Apache2-Access", "kibanaSavedObjectMeta": { "searchSourceJSON": "{}" } -} \ No newline at end of file +} diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/apache2/kibana/visualization/ML-Apache2-Access-Response-Code-Timechart.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache2/kibana/visualization/ML-Apache2-Access-Response-Code-Timechart.json index f52019014b138..bc0a22685d5cf 100644 --- a/x-pack/plugins/ml/server/models/data_recognizer/modules/apache2/kibana/visualization/ML-Apache2-Access-Response-Code-Timechart.json +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache2/kibana/visualization/ML-Apache2-Access-Response-Code-Timechart.json @@ -1,11 +1,12 @@ { - "visState": "{\"title\":\"ML Apache2 Access Response Code Timechart\",\"type\":\"histogram\",\"params\":{\"shareYAxis\":true,\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"scale\":\"linear\",\"mode\":\"stacked\",\"times\":[],\"addTimeMarker\":false,\"defaultYExtents\":false,\"setYExtents\":false,\"yAxis\":{}},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"date_histogram\",\"schema\":\"segment\",\"params\":{\"field\":\"@timestamp\",\"interval\":\"auto\",\"customInterval\":\"2h\",\"min_doc_count\":1,\"extended_bounds\":{}}},{\"id\":\"3\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"group\",\"params\":{\"field\":\"apache2.access.response_code\",\"size\":5,\"order\":\"desc\",\"orderBy\":\"1\"}}],\"listeners\":{}}", - "description": "", - "title": "ML Apache2 Access Response Code Timechart", - "uiStateJSON": "{\n \"vis\": {\n \"colors\": {\n \"200\": \"#7EB26D\",\n \"404\": \"#614D93\"\n }\n }\n}", - "version": 1, - "savedSearchId": "ML-Filebeat-Apache2-Access", + "visState": "{\"title\":\"ML Apache2 Access Response Code Timechart\",\"type\":\"histogram\",\"params\":{\"shareYAxis\":true,\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"scale\":\"linear\",\"mode\":\"stacked\",\"times\":[],\"addTimeMarker\":false,\"defaultYExtents\":false,\"setYExtents\":false,\"yAxis\":{}},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"date_histogram\",\"schema\":\"segment\",\"params\":{\"field\":\"@timestamp\",\"interval\":\"auto\",\"customInterval\":\"2h\",\"min_doc_count\":1,\"extended_bounds\":{}}},{\"id\":\"3\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"group\",\"params\":{\"field\":\"apache2.access.response_code\",\"size\":5,\"order\":\"desc\",\"orderBy\":\"1\"}}],\"listeners\":{}}", + "description": "", + "title": "ML Apache2 Access Response Code Timechart", + "uiStateJSON": "{\n \"vis\": {\n \"colors\": {\n \"200\": \"#7EB26D\",\n \"404\": \"#614D93\"\n }\n }\n}", + "version": 1, + "migrationVersion": {}, + "savedSearchId": "ML-Filebeat-Apache2-Access", "kibanaSavedObjectMeta": { "searchSourceJSON": "{\"filter\":[]}" } -} \ No newline at end of file +} diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/apache2/kibana/visualization/ML-Apache2-Access-Top-Remote-IPs-Table.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache2/kibana/visualization/ML-Apache2-Access-Top-Remote-IPs-Table.json index 0c49d9de46001..38943fd9ee6ac 100644 --- a/x-pack/plugins/ml/server/models/data_recognizer/modules/apache2/kibana/visualization/ML-Apache2-Access-Top-Remote-IPs-Table.json +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache2/kibana/visualization/ML-Apache2-Access-Top-Remote-IPs-Table.json @@ -1,11 +1,12 @@ { - "visState": "{\"title\":\"ML Apache2 Access Top Remote IPs Table\",\"type\":\"table\",\"params\":{\"perPage\":10,\"showPartialRows\":false,\"showMetricsAtAllLevels\":false,\"sort\":{\"columnIndex\":null,\"direction\":null},\"showTotal\":false,\"totalFunc\":\"sum\"},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"bucket\",\"params\":{\"field\":\"apache2.access.remote_ip\",\"size\":5,\"order\":\"desc\",\"orderBy\":\"1\"}}],\"listeners\":{}}", - "description": "", - "title": "ML Apache2 Access Top Remote IPs Table", - "uiStateJSON": "{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":null,\"direction\":null}}}}", - "version": 1, - "savedSearchId": "ML-Filebeat-Apache2-Access", + "visState": "{\"title\":\"ML Apache2 Access Top Remote IPs Table\",\"type\":\"table\",\"params\":{\"perPage\":10,\"showPartialRows\":false,\"showMetricsAtAllLevels\":false,\"sort\":{\"columnIndex\":null,\"direction\":null},\"showTotal\":false,\"totalFunc\":\"sum\"},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"bucket\",\"params\":{\"field\":\"apache2.access.remote_ip\",\"size\":5,\"order\":\"desc\",\"orderBy\":\"1\"}}],\"listeners\":{}}", + "description": "", + "title": "ML Apache2 Access Top Remote IPs Table", + "uiStateJSON": "{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":null,\"direction\":null}}}}", + "version": 1, + "migrationVersion": {}, + "savedSearchId": "ML-Filebeat-Apache2-Access", "kibanaSavedObjectMeta": { "searchSourceJSON": "{}" } -} \ No newline at end of file +} diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/apache2/kibana/visualization/ML-Apache2-Access-Top-URLs-Table.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache2/kibana/visualization/ML-Apache2-Access-Top-URLs-Table.json index ab7760feff999..406c314787c72 100644 --- a/x-pack/plugins/ml/server/models/data_recognizer/modules/apache2/kibana/visualization/ML-Apache2-Access-Top-URLs-Table.json +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache2/kibana/visualization/ML-Apache2-Access-Top-URLs-Table.json @@ -1,11 +1,12 @@ { - "visState": "{\"title\":\"ML Apache2 Access Top URLs Table\",\"type\":\"table\",\"params\":{\"perPage\":100,\"showPartialRows\":false,\"showMetricsAtAllLevels\":false,\"sort\":{\"columnIndex\":null,\"direction\":null},\"showTotal\":false,\"totalFunc\":\"sum\"},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"bucket\",\"params\":{\"field\":\"apache2.access.url\",\"size\":1000,\"order\":\"desc\",\"orderBy\":\"1\"}}],\"listeners\":{}}", - "description": "", - "title": "ML Apache2 Access Top URLs Table", - "uiStateJSON": "{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":null,\"direction\":null}}}}", - "version": 1, - "savedSearchId": "ML-Filebeat-Apache2-Access", + "visState": "{\"title\":\"ML Apache2 Access Top URLs Table\",\"type\":\"table\",\"params\":{\"perPage\":100,\"showPartialRows\":false,\"showMetricsAtAllLevels\":false,\"sort\":{\"columnIndex\":null,\"direction\":null},\"showTotal\":false,\"totalFunc\":\"sum\"},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"bucket\",\"params\":{\"field\":\"apache2.access.url\",\"size\":1000,\"order\":\"desc\",\"orderBy\":\"1\"}}],\"listeners\":{}}", + "description": "", + "title": "ML Apache2 Access Top URLs Table", + "uiStateJSON": "{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":null,\"direction\":null}}}}", + "version": 1, + "migrationVersion": {}, + "savedSearchId": "ML-Filebeat-Apache2-Access", "kibanaSavedObjectMeta": { "searchSourceJSON": "{}" } -} \ No newline at end of file +} diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/apache2/kibana/visualization/ML-Apache2-Access-Unique-Count-URL-Timechart.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache2/kibana/visualization/ML-Apache2-Access-Unique-Count-URL-Timechart.json index c6ce4fcf741eb..54d324434925c 100644 --- a/x-pack/plugins/ml/server/models/data_recognizer/modules/apache2/kibana/visualization/ML-Apache2-Access-Unique-Count-URL-Timechart.json +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache2/kibana/visualization/ML-Apache2-Access-Unique-Count-URL-Timechart.json @@ -1,11 +1,12 @@ { - "visState": "{\"title\":\"ML Apache2 Access Unique Count URL Timechart\",\"type\":\"line\",\"params\":{\"grid\":{\"categoryLines\":false,\"style\":{\"color\":\"#eee\"}},\"categoryAxes\":[{\"id\":\"CategoryAxis-1\",\"type\":\"category\",\"position\":\"bottom\",\"show\":true,\"style\":{},\"scale\":{\"type\":\"linear\"},\"labels\":{\"show\":true,\"truncate\":100},\"title\":{\"text\":\"@timestamp per day\"}}],\"valueAxes\":[{\"id\":\"ValueAxis-1\",\"name\":\"LeftAxis-1\",\"type\":\"value\",\"position\":\"left\",\"show\":true,\"style\":{},\"scale\":{\"type\":\"linear\",\"mode\":\"normal\"},\"labels\":{\"show\":true,\"rotate\":0,\"filter\":false,\"truncate\":100},\"title\":{\"text\":\"Unique count of apache2.access.url\"}}],\"seriesParams\":[{\"show\":true,\"mode\":\"normal\",\"type\":\"line\",\"drawLinesBetweenPoints\":true,\"showCircles\":true,\"interpolate\":\"linear\",\"lineWidth\":2,\"data\":{\"id\":\"1\",\"label\":\"Unique count of apache2.access.url\"},\"valueAxis\":\"ValueAxis-1\"}],\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"showCircles\":true,\"interpolate\":\"linear\",\"scale\":\"linear\",\"drawLinesBetweenPoints\":true,\"radiusRatio\":9,\"times\":[],\"addTimeMarker\":false,\"defaultYExtents\":false,\"setYExtents\":false},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"cardinality\",\"schema\":\"metric\",\"params\":{\"field\":\"apache2.access.url\"}},{\"id\":\"2\",\"enabled\":true,\"type\":\"date_histogram\",\"schema\":\"segment\",\"params\":{\"field\":\"@timestamp\",\"interval\":\"auto\",\"customInterval\":\"2h\",\"min_doc_count\":1,\"extended_bounds\":{}}}],\"listeners\":{}}", - "description": "", - "title": "ML Apache2 Access Unique Count URL Timechart", - "uiStateJSON": "{}", - "version": 1, - "savedSearchId": "ML-Filebeat-Apache2-Access", + "visState": "{\"title\":\"ML Apache2 Access Unique Count URL Timechart\",\"type\":\"line\",\"params\":{\"grid\":{\"categoryLines\":false,\"style\":{\"color\":\"#eee\"}},\"categoryAxes\":[{\"id\":\"CategoryAxis-1\",\"type\":\"category\",\"position\":\"bottom\",\"show\":true,\"style\":{},\"scale\":{\"type\":\"linear\"},\"labels\":{\"show\":true,\"truncate\":100},\"title\":{\"text\":\"@timestamp per day\"}}],\"valueAxes\":[{\"id\":\"ValueAxis-1\",\"name\":\"LeftAxis-1\",\"type\":\"value\",\"position\":\"left\",\"show\":true,\"style\":{},\"scale\":{\"type\":\"linear\",\"mode\":\"normal\"},\"labels\":{\"show\":true,\"rotate\":0,\"filter\":false,\"truncate\":100},\"title\":{\"text\":\"Unique count of apache2.access.url\"}}],\"seriesParams\":[{\"show\":true,\"mode\":\"normal\",\"type\":\"line\",\"drawLinesBetweenPoints\":true,\"showCircles\":true,\"interpolate\":\"linear\",\"lineWidth\":2,\"data\":{\"id\":\"1\",\"label\":\"Unique count of apache2.access.url\"},\"valueAxis\":\"ValueAxis-1\"}],\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"showCircles\":true,\"interpolate\":\"linear\",\"scale\":\"linear\",\"drawLinesBetweenPoints\":true,\"radiusRatio\":9,\"times\":[],\"addTimeMarker\":false,\"defaultYExtents\":false,\"setYExtents\":false},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"cardinality\",\"schema\":\"metric\",\"params\":{\"field\":\"apache2.access.url\"}},{\"id\":\"2\",\"enabled\":true,\"type\":\"date_histogram\",\"schema\":\"segment\",\"params\":{\"field\":\"@timestamp\",\"interval\":\"auto\",\"customInterval\":\"2h\",\"min_doc_count\":1,\"extended_bounds\":{}}}],\"listeners\":{}}", + "description": "", + "title": "ML Apache2 Access Unique Count URL Timechart", + "uiStateJSON": "{}", + "version": 1, + "migrationVersion": {}, + "savedSearchId": "ML-Filebeat-Apache2-Access", "kibanaSavedObjectMeta": { "searchSourceJSON": "{}" } -} \ No newline at end of file +} diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/dashboard/ml_http_access_explorer_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/dashboard/ml_http_access_explorer_ecs.json new file mode 100644 index 0000000000000..681ad572417f6 --- /dev/null +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/dashboard/ml_http_access_explorer_ecs.json @@ -0,0 +1,13 @@ +{ + "hits": 0, + "timeRestore": false, + "description": "", + "title": "ML HTTP Access Explorer (ECS)", + "uiStateJSON": "{\"P-3\":{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":null,\"direction\":null}}}},\"P-5\":{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":null,\"direction\":null}}}}}", + "panelsJSON": "[{\"size_x\":6,\"size_y\":3,\"panelIndex\":1,\"type\":\"visualization\",\"id\":\"ml_http_access_events_timechart_ecs\",\"col\":1,\"row\":1},{\"size_x\":6,\"size_y\":3,\"panelIndex\":2,\"type\":\"visualization\",\"id\":\"ml_http_access_unique_count_url_timechart_ecs\",\"col\":7,\"row\":1},{\"size_x\":6,\"size_y\":3,\"panelIndex\":3,\"type\":\"visualization\",\"id\":\"ml_http_access_status_code_timechart_ecs\",\"col\":1,\"row\":4},{\"size_x\":6,\"size_y\":3,\"panelIndex\":4,\"type\":\"visualization\",\"id\":\"ml_http_access_source_ip_timechart_ecs\",\"col\":7,\"row\":4},{\"size_x\":6,\"size_y\":3,\"panelIndex\":5,\"type\":\"visualization\",\"id\":\"ml_http_access_top_source_ips_table_ecs\",\"col\":1,\"row\":8},{\"size_x\":6,\"size_y\":3,\"panelIndex\":6,\"type\":\"visualization\",\"id\":\"ml_http_access_map_ecs\",\"col\":7,\"row\":8},{\"size_x\":12,\"size_y\":9,\"panelIndex\":7,\"type\":\"visualization\",\"id\":\"ml_http_access_top_urls_table_ecs\",\"col\":1,\"row\":11}]", + "optionsJSON": "{\"darkTheme\":false}", + "version": 1, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\n \"filter\": [],\n \"highlightAll\": true,\n \"version\": true,\n \"query\": {\n \"query\": \"\",\n \"language\": \"lucene\"\n }\n}" + } +} \ No newline at end of file diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/search/ml_http_access_filebeat_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/search/ml_http_access_filebeat_ecs.json new file mode 100644 index 0000000000000..6575c7e65d78d --- /dev/null +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/search/ml_http_access_filebeat_ecs.json @@ -0,0 +1,16 @@ +{ + "sort": [ + "@timestamp", + "desc" + ], + "hits": 0, + "description": "Filebeat HTTP Access Data (ECS)", + "title": "ML HTTP Access Data (ECS)", + "version": 1, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"index\":\"INDEX_PATTERN_ID\",\"query\":{\"query_string\":{\"query\":\"fileset.name:access\",\"analyze_wildcard\":true}},\"filter\":[],\"highlight\":{\"pre_tags\":[\"@kibana-highlighted-field@\"],\"post_tags\":[\"@/kibana-highlighted-field@\"],\"fields\":{\"*\":{}},\"require_field_match\":false,\"fragment_size\":2147483647}}" + }, + "columns": [ + "_source" + ] +} diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_events_timechart_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_events_timechart_ecs.json new file mode 100644 index 0000000000000..ff507da41d1a5 --- /dev/null +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_events_timechart_ecs.json @@ -0,0 +1,11 @@ +{ + "visState": "{\"title\":\"ML HTTP Access Event Timechart (ECS)\",\"type\":\"histogram\",\"params\":{\"shareYAxis\":true,\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"scale\":\"linear\",\"mode\":\"stacked\",\"times\":[],\"addTimeMarker\":false,\"defaultYExtents\":false,\"setYExtents\":false,\"yAxis\":{}},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"date_histogram\",\"schema\":\"segment\",\"params\":{\"field\":\"@timestamp\",\"interval\":\"auto\",\"customInterval\":\"2h\",\"min_doc_count\":1,\"extended_bounds\":{}}},{\"id\":\"3\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"group\",\"params\":{\"field\":\"event.module\",\"size\":10,\"order\":\"desc\",\"orderBy\":\"1\"}},{\"id\":\"4\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"group\",\"params\":{\"field\":\"event.dataset\",\"size\":10,\"order\":\"desc\",\"orderBy\":\"1\"}}],\"listeners\":{}}", + "description": "", + "title": "ML HTTP Access Event Timechart (ECS)", + "uiStateJSON": "{\"vis\":{\"colors\":{\"apache - access\":\"#629E51\",\"nginx - access\":\"#1F78C1\"}}}", + "version": 1, + "savedSearchId": "ml_http_access_filebeat_ecs", + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + } + } \ No newline at end of file diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_map_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_map_ecs.json new file mode 100644 index 0000000000000..4cf3982f5ac6a --- /dev/null +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_map_ecs.json @@ -0,0 +1,11 @@ +{ + "visState": "{\"aggs\":[{\"enabled\":true,\"id\":\"1\",\"params\":{},\"schema\":\"metric\",\"type\":\"count\"},{\"enabled\":true,\"id\":\"2\",\"params\":{\"autoPrecision\":true,\"field\":\"source.geo.location\"},\"schema\":\"segment\",\"type\":\"geohash_grid\"}],\"listeners\":{},\"params\":{\"addTooltip\":true,\"heatBlur\":15,\"heatMaxZoom\":16,\"heatMinOpacity\":0.1,\"heatNormalizeData\":true,\"heatRadius\":25,\"isDesaturated\":true,\"legendPosition\":\"bottomright\",\"mapCenter\":[15,5],\"mapType\":\"Scaled Circle Markers\",\"mapZoom\":2,\"wms\":{\"enabled\":false,\"options\":{\"attribution\":\"Maps provided by USGS\",\"format\":\"image/png\",\"layers\":\"0\",\"styles\":\"\",\"transparent\":true,\"version\":\"1.3.0\"},\"url\":\"https://basemap.nationalmap.gov/arcgis/services/USGSTopo/MapServer/WMSServer\"}},\"title\":\"ML HTTP Access Map (ECS)\",\"type\":\"tile_map\"}", + "description": "", + "title": "ML HTTP Access Map (ECS)", + "uiStateJSON": "{\n \"mapCenter\": [\n 12.039320557540572,\n -0.17578125\n ]\n}", + "version": 1, + "savedSearchId": "ml_http_access_filebeat_ecs", + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + } +} \ No newline at end of file diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_source_ip_timechart_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_source_ip_timechart_ecs.json new file mode 100644 index 0000000000000..7525752abb053 --- /dev/null +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_source_ip_timechart_ecs.json @@ -0,0 +1,11 @@ +{ + "visState": "{\"title\":\"ML HTTP Access Source IP Timechart (ECS)\",\"type\":\"area\",\"params\":{\"addLegend\":true,\"addTimeMarker\":false,\"addTooltip\":true,\"categoryAxes\":[{\"id\":\"CategoryAxis-1\",\"labels\":{\"show\":true,\"truncate\":100},\"position\":\"bottom\",\"scale\":{\"type\":\"linear\"},\"show\":true,\"style\":{},\"title\":{\"text\":\"@timestamp per 5 minutes\"},\"type\":\"category\"}],\"defaultYExtents\":false,\"drawLinesBetweenPoints\":true,\"grid\":{\"categoryLines\":false,\"style\":{\"color\":\"#eee\"}},\"interpolate\":\"linear\",\"legendPosition\":\"right\",\"radiusRatio\":9,\"scale\":\"linear\",\"seriesParams\":[{\"data\":{\"id\":\"1\",\"label\":\"Count\"},\"drawLinesBetweenPoints\":true,\"interpolate\":\"linear\",\"mode\":\"stacked\",\"show\":\"true\",\"showCircles\":true,\"type\":\"area\",\"valueAxis\":\"ValueAxis-1\"}],\"setYExtents\":false,\"showCircles\":true,\"times\":[],\"valueAxes\":[{\"id\":\"ValueAxis-1\",\"labels\":{\"filter\":false,\"rotate\":0,\"show\":true,\"truncate\":100},\"name\":\"LeftAxis-1\",\"position\":\"left\",\"scale\":{\"mode\":\"normal\",\"type\":\"linear\"},\"show\":true,\"style\":{},\"title\":{},\"type\":\"value\"}]},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"date_histogram\",\"schema\":\"segment\",\"params\":{\"field\":\"@timestamp\",\"interval\":\"auto\",\"customInterval\":\"2h\",\"min_doc_count\":1,\"extended_bounds\":{}}},{\"id\":\"3\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"group\",\"params\":{\"field\":\"source.address\",\"size\":5,\"order\":\"desc\",\"orderBy\":\"1\"}}],\"listeners\":{}}", + "description": "", + "title": "ML HTTP Access Source IP Timechart (ECS)", + "uiStateJSON": "{}", + "version": 1, + "savedSearchId": "ml_http_access_filebeat_ecs", + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + } +} diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_status_code_timechart_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_status_code_timechart_ecs.json new file mode 100644 index 0000000000000..e104b047f924b --- /dev/null +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_status_code_timechart_ecs.json @@ -0,0 +1,11 @@ +{ + "visState": "{\"title\":\"ML HTTP Access Status Code Timechart (ECS)\",\"type\":\"histogram\",\"params\":{\"shareYAxis\":true,\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"scale\":\"linear\",\"mode\":\"stacked\",\"times\":[],\"addTimeMarker\":false,\"defaultYExtents\":false,\"setYExtents\":false,\"yAxis\":{}},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"date_histogram\",\"schema\":\"segment\",\"params\":{\"field\":\"@timestamp\",\"interval\":\"auto\",\"customInterval\":\"2h\",\"min_doc_count\":1,\"extended_bounds\":{}}},{\"id\":\"3\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"group\",\"params\":{\"field\":\"http.response.status_code\",\"size\":50,\"order\":\"desc\",\"orderBy\":\"1\"}}],\"listeners\":{}}", + "description": "", + "title": "ML HTTP Access Status Code Timechart (ECS)", + "uiStateJSON": "{\n \"vis\": {\n \"colors\": {\n \"200\": \"#7EB26D\",\n \"404\": \"#614D93\"\n }\n }\n}", + "version": 1, + "savedSearchId": "ml_http_access_filebeat_ecs", + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + } +} \ No newline at end of file diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_top_source_ips_table_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_top_source_ips_table_ecs.json new file mode 100644 index 0000000000000..c9afd66ea39b1 --- /dev/null +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_top_source_ips_table_ecs.json @@ -0,0 +1,11 @@ +{ + "visState": "{\"title\":\"ML HTTP Access Top Source IPs Table (ECS)\",\"type\":\"table\",\"params\":{\"perPage\":10,\"showPartialRows\":false,\"showMetricsAtAllLevels\":false,\"sort\":{\"columnIndex\":null,\"direction\":null},\"showTotal\":false,\"totalFunc\":\"sum\"},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"bucket\",\"params\":{\"field\":\"source.address\",\"size\":50,\"order\":\"desc\",\"orderBy\":\"1\"}}],\"listeners\":{}}", + "description": "", + "title": "ML HTTP Access Top Source IPs Table (ECS)", + "uiStateJSON": "{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":null,\"direction\":null}}}}", + "version": 1, + "savedSearchId": "ml_http_access_filebeat_ecs", + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + } +} \ No newline at end of file diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_top_urls_table_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_top_urls_table_ecs.json new file mode 100644 index 0000000000000..34fbcb621085c --- /dev/null +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_top_urls_table_ecs.json @@ -0,0 +1,11 @@ +{ + "visState": "{\"title\":\"ML HTTP Access Top URLs Table (ECS)\",\"type\":\"table\",\"params\":{\"perPage\":100,\"showPartialRows\":false,\"showMetricsAtAllLevels\":false,\"sort\":{\"columnIndex\":null,\"direction\":null},\"showTotal\":false,\"totalFunc\":\"sum\"},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"bucket\",\"params\":{\"field\":\"url.original\",\"size\":1000,\"order\":\"desc\",\"orderBy\":\"1\"}}],\"listeners\":{}}", + "description": "", + "title": "ML HTTP Access Top URLs Table (ECS)", + "uiStateJSON": "{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":null,\"direction\":null}}}}", + "version": 1, + "savedSearchId": "ml_http_access_filebeat_ecs", + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + } +} \ No newline at end of file diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_unique_count_url_timechart_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_unique_count_url_timechart_ecs.json new file mode 100644 index 0000000000000..d14592e68bb6e --- /dev/null +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_unique_count_url_timechart_ecs.json @@ -0,0 +1,11 @@ +{ + "visState": "{\"title\":\"ML HTTP Access Unique Count URL Timechart (ECS)\",\"type\":\"line\",\"params\":{\"grid\":{\"categoryLines\":false,\"style\":{\"color\":\"#eee\"}},\"categoryAxes\":[{\"id\":\"CategoryAxis-1\",\"type\":\"category\",\"position\":\"bottom\",\"show\":true,\"style\":{},\"scale\":{\"type\":\"linear\"},\"labels\":{\"show\":true,\"truncate\":100},\"title\":{\"text\":\"@timestamp per day\"}}],\"valueAxes\":[{\"id\":\"ValueAxis-1\",\"name\":\"LeftAxis-1\",\"type\":\"value\",\"position\":\"left\",\"show\":true,\"style\":{},\"scale\":{\"type\":\"linear\",\"mode\":\"normal\"},\"labels\":{\"show\":true,\"rotate\":0,\"filter\":false,\"truncate\":100},\"title\":{\"text\":\"Unique count of url.original\"}}],\"seriesParams\":[{\"show\":true,\"mode\":\"normal\",\"type\":\"line\",\"drawLinesBetweenPoints\":true,\"showCircles\":true,\"interpolate\":\"linear\",\"lineWidth\":2,\"data\":{\"id\":\"1\",\"label\":\"Unique count of url.original\"},\"valueAxis\":\"ValueAxis-1\"}],\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"showCircles\":true,\"interpolate\":\"linear\",\"scale\":\"linear\",\"drawLinesBetweenPoints\":true,\"radiusRatio\":9,\"times\":[],\"addTimeMarker\":false,\"defaultYExtents\":false,\"setYExtents\":false},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"cardinality\",\"schema\":\"metric\",\"params\":{\"field\":\"url.original\"}},{\"id\":\"2\",\"enabled\":true,\"type\":\"date_histogram\",\"schema\":\"segment\",\"params\":{\"field\":\"@timestamp\",\"interval\":\"auto\",\"customInterval\":\"2h\",\"min_doc_count\":1,\"extended_bounds\":{}}}],\"listeners\":{}}", + "description": "", + "title": "ML HTTP Access Unique Count URL Timechart (ECS)", + "uiStateJSON": "{}", + "version": 1, + "savedSearchId": "ml_http_access_filebeat_ecs", + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + } +} \ No newline at end of file diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/logo.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/logo.json new file mode 100644 index 0000000000000..e15620ed29fcc --- /dev/null +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/logo.json @@ -0,0 +1,6 @@ +{ + "src": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAA5CAIAAADYwb7wAAAHTUlEQVR4AbXTa1iT5xkH8Lvt7BTFtXioxjcJggIiHkQFUQQFJgdEUGB27arOQxHdCgNdUStFC1OrbR3latU5kIAogoKUg1JQAjkgkIQEAjSExBBCSAIkkDM5sMf5rR/64ZU81/vhzvX+r/v33s/zBKZnYtlVPHOhl/EGwabo+dUrmInu3aYrYM4Dwzcw+QXYdaoZBTQi/eVX3c233zbdnKfNAkNxzMwBZo3+azD++1V3c76T6SYYrsPkFZg2js0EYLOidsbvwHQLpu76ThV5mhHwPWgvARrLbre/KTCRG69Jh6kCMJUGTlXuM99Cc4DpRzBfhWm9wmq1cbg8iUSCEzAz78oTYfQ0mAq9ppnpZoqTpQCm8sH4AxivoS1So8zYuFqhUOIBrAqxbB8oU2cp/gbG6kxjUfhENkzkOKuznEfTQH01eNpux38GdptVdgBkB0F+BLR3UvTlWYpjoEpbqEpzUZ1aOHIAjI15b3SL1DfTxBEwfGSJMiPQ0ERRnnZXpswfPb1g7HOX8Qxn5TGwjUnxA+Z+jsAfhg57SBJA/zRfczt16ACMpBAVqUtUp5YqjsJE7m78/2S71SIMf1e0e554z2zVt8n65/cG/wSypOXDyST5SeLIZ5gsAaxDvfgBVe65nrUg3u8zEOViaH6szP5Ikug0dHiF7Ijb8HG3wXgYzY5GMZyARSrikUG427tvy9vjd67qfi4VhsDgXzyln6yQHnIfOkQWh4FVJcUL2O09fht6NswV7FrVH71Gz2iQJkcKo4niRK+X+z0HD3j3B4D6zpcoiBPQPKigAfQGb+ryfEtdckvzuLhnHQzE+ojiVokTVgsCYfDgGvQROAGb0dgGi1ngwVu++pfIAH1ri2h/iGCHe3+UtzDGqz+CMJJ10KpWoSROYPh8Dh2At8L/BThr7t+frCnnEqBv59q+nT59OzwHj+8fu5E3WVdnt1rxABbVKANmsz9Yx17k07V2p57G7A0K5WLePLf13OUbuO6+Pf6hDICJigqcE7xMTmeCU6e7Xyu8P1Zwd/zeQwbM78Q2dy7x5RJ8uZgvC1w5Lp7TFgsewCIbocHvOYQN7KXrOkkbtejzQ2NZ8705mC8H29BJ8uWSN9IAtPWNKIwHEB/9jA7OHDd/JixQ3aZM1NYz0ee7bkQA8tC+obFkZy6gJB5galBGhfdYyzaxl25sn+OtY3YIEw93zPLoxF4BnCXr+JvCRH89ae4X4QQkGTlNQGgnb2UAceSbHyYbqQxw4ZD8ONgm9KB94/v9kesRYJHJ8QDWSe1zWE13CXhB2t4M7loqU5KW2QrubDQQesibO+Z602G2rqkFhfEA8vzyOnClk0OaYJ0wJUvLYLXAyjZSQAfmx1q2uWPxen5wrDLvNkriAew2W8vK6EbY0kIKeworNc+ZwzeLnsGKVuL2F9jWNmxLG2Ezb3OE+PgpdDvxANouQRX4NGHhjfOCWFGfalndNCyk5YMgOhbEwIJalwW2uQY+gzljxWU4JxBcuFEB6xtdo6tgjbKyQVXVWANezeSwFmIIDdtJx4LR2VDB26bV4QFsFkuda1z1nNCnhOgnfwjTvOCx4v7e4BTchO2iYmHNy0L/v29uA2k5KIwHmOgSlsG2WmLcI9gmvEZR0zmV4NtAjmokRjzDwhHTRAitgZVmqRwnMHCjsgS2V5MTSmHrOJMnvFZYCdvqSTH1pKifiZHP3HdXgkdv2mWUxAlQYzNKIfKhczQj4aya3ff4vegawp46YuwTLKaevKfmnR2VsNZmMOEErFpD+eL4soXxFNg+/BNdXk27CwHVpPhq0t5aclwtIaYIlqtpHJTECYzypXcg7uHSxPtOcWMdvfQ/Z5XNiakkJlSREquW7i0Ar4HcEhTDD3Q/Yn0HH5dAJPcrymh7HwV2PSJ9WOH64YM5MRTwF+WVogwewGa1qfrkqGAXtFx8N+lHiBxl8oUFTwog/P77CRQIfbRgr/YXyeskzgmEDfzWvAZm7tMv3kku3Hp+omvgJ5+jFa6f0D/+Sl7bajNbNNJxLoWOH0DrvxHfZs46cQ4+5dxrk9F6L8G+yvhr/GKatE30+GTxxd8lTemMKIYfUPWNpMOxc28lyzsHGy7VZcz9x79WZmYtSj8Fh66vOmPU6FEGJ2C32V//uLLqy+8DLsu40gvY5zluZ3NIGdfXZ1G/rhvqEKO3+IEpk0XS/lLGHcrEzrRTmP1UQSqcyPY4f5GYcXZu6vWN2Qq+7I0AtPi13Ulw4iScUAmU+R/lJ8HxM4v+eX5ZRunRwmHOIArgB9DFkHXLO8rYvfW9jHwm+yGnNKUM1cImgVYx+es4PkA3bnh9EkK6SDeuk7ClOJv99i0SNA/waviosE5ZTTrTzAPNBW1olN9O4wdGpZqmgnZUOArg1gu6GoQOBJpLuNpxg6OAyTED9QEPFY4CWM2CDqrAgUD5vRaVcsJRgFZrKC5qnHbYAiazt6623YHAf27ViUQjDgTo9J5pR67/AfRrVpVr6wheAAAAAElFTkSuQmCC", + "height": 25, + "width": 125 +} + diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/manifest.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/manifest.json new file mode 100644 index 0000000000000..031cf05ca39cd --- /dev/null +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/manifest.json @@ -0,0 +1,111 @@ +{ + "id": "apache_ecs", + "title": "Apache access logs", + "description": "Find unusual activity in HTTP access logs from filebeat (ECS)", + "type": "Web Access Logs", + "logoFile": "logo.json", + "defaultIndexPattern": "filebeat-*", + "query": { + "bool": { + "filter": [ + { "term": { "event.dataset": "apache.access" } }, + { "exists": { "field": "source.address" } }, + { "exists": { "field": "url.original" } }, + { "exists": { "field": "http.response.status_code" } } + ] + } + }, + "jobs": [ + { + "id": "visitor_rate_ecs", + "file": "visitor_rate_ecs.json" + }, + { + "id": "status_code_rate_ecs", + "file": "status_code_rate_ecs.json" + }, + { + "id": "source_ip_url_count_ecs", + "file": "source_ip_url_count_ecs.json" + }, + { + "id": "source_ip_request_rate_ecs", + "file": "source_ip_request_rate_ecs.json" + }, + { + "id": "low_request_rate_ecs", + "file": "low_request_rate_ecs.json" + } + ], + "datafeeds": [ + { + "id": "datafeed-visitor_rate_ecs", + "file": "datafeed_visitor_rate_ecs.json", + "job_id": "visitor_rate_ecs" + }, + { + "id": "datafeed-status_code_rate_ecs", + "file": "datafeed_status_code_rate_ecs.json", + "job_id": "status_code_rate_ecs" + }, + { + "id": "datafeed-source_ip_url_count_ecs", + "file": "datafeed_source_ip_url_count_ecs.json", + "job_id": "source_ip_url_count_ecs" + }, + { + "id": "datafeed-source_ip_request_rate_ecs", + "file": "datafeed_source_ip_request_rate_ecs.json", + "job_id": "source_ip_request_rate_ecs" + }, + { + "id": "datafeed-low_request_rate_ecs", + "file": "datafeed_low_request_rate_ecs.json", + "job_id": "low_request_rate_ecs" + } + ], + "kibana": { + "dashboard": [ + { + "id": "ml_http_access_explorer_ecs", + "file": "ml_http_access_explorer_ecs.json" + } + ], + "search": [ + { + "id": "ml_http_access_filebeat_ecs", + "file": "ml_http_access_filebeat_ecs.json" + } + ], + "visualization": [ + { + "id": "ml_http_access_map_ecs", + "file": "ml_http_access_map_ecs.json" + }, + { + "id": "ml_http_access_source_ip_timechart_ecs", + "file": "ml_http_access_source_ip_timechart_ecs.json" + }, + { + "id": "ml_http_access_status_code_timechart_ecs", + "file": "ml_http_access_status_code_timechart_ecs.json" + }, + { + "id": "ml_http_access_top_source_ips_table_ecs", + "file": "ml_http_access_top_source_ips_table_ecs.json" + }, + { + "id": "ml_http_access_top_urls_table_ecs", + "file": "ml_http_access_top_urls_table_ecs.json" + }, + { + "id": "ml_http_access_unique_count_url_timechart_ecs", + "file": "ml_http_access_unique_count_url_timechart_ecs.json" + }, + { + "id": "ml_http_access_events_timechart_ecs", + "file": "ml_http_access_events_timechart_ecs.json" + } + ] + } +} diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_low_request_rate_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_low_request_rate_ecs.json new file mode 100644 index 0000000000000..b08a8526b17e5 --- /dev/null +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_low_request_rate_ecs.json @@ -0,0 +1,34 @@ +{ + "job_id": "JOB_ID", + "indexes": [ + "INDEX_PATTERN_NAME" + ], + "query": { + "bool": { + "filter": [ + { "term": { "event.dataset": "apache.access" } } + ] + } + }, + "aggregations": { + "buckets": { + "date_histogram": { + "field": "@timestamp", + "interval": 900000, + "offset": 0, + "order": { + "_key": "asc" + }, + "keyed": false, + "min_doc_count": 0 + }, + "aggregations": { + "@timestamp": { + "max": { + "field": "@timestamp" + } + } + } + } + } +} diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_source_ip_request_rate_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_source_ip_request_rate_ecs.json new file mode 100644 index 0000000000000..824d6a934d865 --- /dev/null +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_source_ip_request_rate_ecs.json @@ -0,0 +1,13 @@ +{ + "job_id": "JOB_ID", + "indexes": [ + "INDEX_PATTERN_NAME" + ], + "query": { + "bool": { + "filter": [ + { "term": { "event.dataset": "apache.access" } } + ] + } + } +} diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_source_ip_url_count_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_source_ip_url_count_ecs.json new file mode 100644 index 0000000000000..824d6a934d865 --- /dev/null +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_source_ip_url_count_ecs.json @@ -0,0 +1,13 @@ +{ + "job_id": "JOB_ID", + "indexes": [ + "INDEX_PATTERN_NAME" + ], + "query": { + "bool": { + "filter": [ + { "term": { "event.dataset": "apache.access" } } + ] + } + } +} diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_status_code_rate_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_status_code_rate_ecs.json new file mode 100644 index 0000000000000..824d6a934d865 --- /dev/null +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_status_code_rate_ecs.json @@ -0,0 +1,13 @@ +{ + "job_id": "JOB_ID", + "indexes": [ + "INDEX_PATTERN_NAME" + ], + "query": { + "bool": { + "filter": [ + { "term": { "event.dataset": "apache.access" } } + ] + } + } +} diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_visitor_rate_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_visitor_rate_ecs.json new file mode 100644 index 0000000000000..b88b66f15b329 --- /dev/null +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_visitor_rate_ecs.json @@ -0,0 +1,39 @@ +{ + "job_id": "JOB_ID", + "indexes": [ + "INDEX_PATTERN_NAME" + ], + "query": { + "bool": { + "filter": [ + { "term": { "event.dataset": "apache.access" } } + ] + } + }, + "aggregations": { + "buckets": { + "date_histogram": { + "field": "@timestamp", + "interval": 900000, + "offset": 0, + "order": { + "_key": "asc" + }, + "keyed": false, + "min_doc_count": 0 + }, + "aggregations": { + "@timestamp": { + "max": { + "field": "@timestamp" + } + }, + "dc_source_address": { + "cardinality": { + "field": "source.address" + } + } + } + } + } +} diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/low_request_rate_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/low_request_rate_ecs.json new file mode 100644 index 0000000000000..1bfb864bef9ee --- /dev/null +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/low_request_rate_ecs.json @@ -0,0 +1,34 @@ +{ + "groups": ["apache"], + "description": "HTTP Access Logs: Detect low request rate (ECS)", + "analysis_config" : { + "bucket_span": "15m", + "summary_count_field_name": "doc_count", + "detectors": [ + { + "detector_description": "apache_access_low_request_rate", + "function": "low_count" + } + ], + "influencers": [] + }, + "analysis_limits": { + "model_memory_limit": "10mb" + }, + "data_description": { + "time_field": "@timestamp", + "time_format": "epoch_ms" + }, + "model_plot_config": { + "enabled": true + }, + "custom_settings": { + "created_by": "ml-module-apache-access", + "custom_urls": [ + { + "url_name": "Raw data", + "url_value": "kibana#/discover/ml_http_access_filebeat_ecs?_g=(time:(from:\u0027$earliest$\u0027,mode:absolute,to:\u0027$latest$\u0027))&_a=(columns:!(_source),filters:!((\u0027$state\u0027:(store:appState),meta:(alias:!n,disabled:!f,index:\u0027INDEX_PATTERN_ID\u0027,key:event.dataset,negate:!f,params:(query:\u0027apache.access\u0027,type:phrase),type:phrase,value:\u0027apache.access\u0027),query:(match:(event.dataset:(query:\u0027apache.access\u0027,type:phrase))))),index:\u0027INDEX_PATTERN_ID\u0027,interval:auto,query:(language:lucene,query:\u0027\u0027),sort:!(\u0027@timestamp\u0027,desc))" + } + ] + } +} diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/source_ip_request_rate_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/source_ip_request_rate_ecs.json new file mode 100644 index 0000000000000..25c51caec8727 --- /dev/null +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/source_ip_request_rate_ecs.json @@ -0,0 +1,34 @@ +{ + "groups": ["apache"], + "description": "HTTP Access Logs: Detect unusual source ips - high request rates (ECS)", + "analysis_config" : { + "bucket_span": "1h", + "detectors": [ + { + "detector_description": "apache_access_source_ip_high_count", + "function": "high_count", + "over_field_name": "source.address" + } + ], + "influencers": [ + "source.address" + ] + }, + "data_description": { + "time_field": "@timestamp", + "time_format": "epoch_ms" + }, + "custom_settings": { + "created_by": "ml-module-apache-access", + "custom_urls": [ + { + "url_name": "Investigate source IP", + "url_value": "kibana#/dashboard/ml_http_access_explorer_ecs?_g=(time:(from:\u0027$earliest$\u0027,mode:absolute,to:\u0027$latest$\u0027))&_a=(description:\u0027\u0027,filters:!((\u0027$state\u0027:(store:appState),meta:(alias:!n,disabled:!f,index:\u0027INDEX_PATTERN_ID\u0027,key:event.dataset,negate:!f,params:(query:\u0027apache.access\u0027,type:phrase),type:phrase,value:\u0027apache.access\u0027),query:(match:(event.dataset:(query:\u0027apache.access\u0027,type:phrase)))),(\u0027$state\u0027:(store:appState),meta:(alias:!n,disabled:!f,index:\u0027INDEX_PATTERN_ID\u0027,key:source.address,negate:!f,type:phrase,value:\u0027$source.address$\u0027),query:(match:(source.address:(query:\u0027$source.address$\u0027,type:phrase))))),query:(language:lucene,query:\u0027\u0027))" + }, + { + "url_name": "Raw data", + "url_value": "kibana#/discover/ml_http_access_filebeat_ecs?_g=(time:(from:\u0027$earliest$\u0027,mode:absolute,to:\u0027$latest$\u0027))&_a=(columns:!(_source),filters:!((\u0027$state\u0027:(store:appState),meta:(alias:!n,disabled:!f,index:\u0027INDEX_PATTERN_ID\u0027,key:event.dataset,negate:!f,params:(query:\u0027apache.access\u0027,type:phrase),type:phrase,value:\u0027apache.access\u0027),query:(match:(event.dataset:(query:\u0027apache.access\u0027,type:phrase)))),(\u0027$state\u0027:(store:appState),meta:(alias:!n,disabled:!f,index:\u0027INDEX_PATTERN_ID\u0027,key:source.address,negate:!f,type:phrase,value:\u0027$source.address$\u0027),query:(match:(source.address:(query:\u0027$source.address$\u0027,type:phrase))))),index:\u0027INDEX_PATTERN_ID\u0027,interval:auto,query:(language:lucene,query:\u0027\u0027),sort:!(\u0027@timestamp\u0027,desc))" + } + ] + } +} diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/source_ip_url_count_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/source_ip_url_count_ecs.json new file mode 100644 index 0000000000000..2982e04771086 --- /dev/null +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/source_ip_url_count_ecs.json @@ -0,0 +1,35 @@ +{ + "groups": ["apache"], + "description": "HTTP Access Logs: Detect unusual source ips - high distinct count of urls (ECS)", + "analysis_config" : { + "bucket_span": "1h", + "detectors": [ + { + "detector_description": "apache_access_source_ip_high_dc_url", + "function": "high_distinct_count", + "field_name": "url.original", + "over_field_name": "source.address" + } + ], + "influencers": [ + "source.address" + ] + }, + "data_description": { + "time_field": "@timestamp", + "time_format": "epoch_ms" + }, + "custom_settings": { + "created_by": "ml-module-apache-access", + "custom_urls": [ + { + "url_name": "Investigate source IP", + "url_value": "kibana#/dashboard/ml_http_access_explorer_ecs?_g=(time:(from:\u0027$earliest$\u0027,mode:absolute,to:\u0027$latest$\u0027))&_a=(description:\u0027\u0027,filters:!((\u0027$state\u0027:(store:appState),meta:(alias:!n,disabled:!f,index:\u0027INDEX_PATTERN_ID\u0027,key:event.dataset,negate:!f,params:(query:\u0027apache.access\u0027,type:phrase),type:phrase,value:\u0027apache.access\u0027),query:(match:(event.dataset:(query:\u0027apache.access\u0027,type:phrase)))),(\u0027$state\u0027:(store:appState),meta:(alias:!n,disabled:!f,index:\u0027INDEX_PATTERN_ID\u0027,key:source.address,negate:!f,type:phrase,value:\u0027$source.address$\u0027),query:(match:(source.address:(query:\u0027$source.address$\u0027,type:phrase))))),query:(language:lucene,query:\u0027\u0027))" + }, + { + "url_name": "Raw data", + "url_value": "kibana#/discover/ml_http_access_filebeat_ecs?_g=(time:(from:\u0027$earliest$\u0027,mode:absolute,to:\u0027$latest$\u0027))&_a=(columns:!(_source),filters:!((\u0027$state\u0027:(store:appState),meta:(alias:!n,disabled:!f,index:\u0027INDEX_PATTERN_ID\u0027,key:event.dataset,negate:!f,params:(query:\u0027apache.access\u0027,type:phrase),type:phrase,value:\u0027apache.access\u0027),query:(match:(event.dataset:(query:\u0027apache.access\u0027,type:phrase)))),(\u0027$state\u0027:(store:appState),meta:(alias:!n,disabled:!f,index:\u0027INDEX_PATTERN_ID\u0027,key:source.address,negate:!f,type:phrase,value:\u0027$source.address$\u0027),query:(match:(source.address:(query:\u0027$source.address$\u0027,type:phrase))))),index:\u0027INDEX_PATTERN_ID\u0027,interval:auto,query:(language:lucene,query:\u0027\u0027),sort:!(\u0027@timestamp\u0027,desc))" + } + ] + } +} diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/status_code_rate_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/status_code_rate_ecs.json new file mode 100644 index 0000000000000..39bf67c71ae31 --- /dev/null +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/status_code_rate_ecs.json @@ -0,0 +1,41 @@ +{ + "groups": ["apache"], + "description": "HTTP Access Logs: Detect unusual status_code rates (ECS)", + "analysis_config" : { + "bucket_span": "15m", + "detectors": [ + { + "detector_description": "apache_access_status_code_rate", + "function": "count", + "partition_field_name": "http.response.status_code" + } + ], + "influencers": [ + "http.response.status_code", + "source.address" + ] + }, + "analysis_limits": { + "model_memory_limit": "100mb" + }, + "data_description": { + "time_field": "@timestamp", + "time_format": "epoch_ms" + }, + "model_plot_config": { + "enabled": true + }, + "custom_settings": { + "created_by": "ml-module-apache-access", + "custom_urls": [ + { + "url_name": "Investigate status code", + "url_value": "kibana#/dashboard/ml_http_access_explorer_ecs?_g=(time:(from:\u0027$earliest$\u0027,mode:absolute,to:\u0027$latest$\u0027))&_a=(description:\u0027\u0027,filters:!((\u0027$state\u0027:(store:appState),meta:(alias:!n,disabled:!f,index:\u0027INDEX_PATTERN_ID\u0027,key:event.dataset,negate:!f,params:(query:\u0027apache.access\u0027,type:phrase),type:phrase,value:\u0027apache.access\u0027),query:(match:(event.dataset:(query:\u0027apache.access\u0027,type:phrase)))),(\u0027$state\u0027:(store:appState),meta:(alias:!n,disabled:!f,index:\u0027INDEX_PATTERN_ID\u0027,key:http.response.status_code,negate:!f,params:(query:\u0027$http.response.status_code$\u0027,type:phrase),type:phrase,value:\u0027$http.response.status_code$\u0027),query:(match:(http.response.status_code:(query:\u0027$http.response.status_code$\u0027,type:phrase))))),query:(language:lucene,query:\u0027\u0027))" + }, + { + "url_name": "Raw data", + "url_value": "kibana#/discover/ml_http_access_filebeat_ecs?_g=(time:(from:\u0027$earliest$\u0027,mode:absolute,to:\u0027$latest$\u0027))&_a=(columns:!(_source),filters:!((\u0027$state\u0027:(store:appState),meta:(alias:!n,disabled:!f,index:\u0027INDEX_PATTERN_ID\u0027,key:event.dataset,negate:!f,params:(query:\u0027apache.access\u0027,type:phrase),type:phrase,value:\u0027apache.access\u0027),query:(match:(event.dataset:(query:\u0027apache.access\u0027,type:phrase)))),(\u0027$state\u0027:(store:appState),meta:(alias:!n,disabled:!f,index:\u0027INDEX_PATTERN_ID\u0027,key:http.response.status_code,negate:!f,type:phrase,value:\u0027$http.response.status_code$\u0027),query:(match:(http.response.status_code:(query:\u0027$http.response.status_code$\u0027,type:phrase))))),index:\u0027INDEX_PATTERN_ID\u0027,interval:auto,query:(language:lucene,query:\u0027\u0027),sort:!(\u0027@timestamp\u0027,desc))" + } + ] + } +} \ No newline at end of file diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/visitor_rate_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/visitor_rate_ecs.json new file mode 100644 index 0000000000000..66ffde6bccf84 --- /dev/null +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/visitor_rate_ecs.json @@ -0,0 +1,34 @@ +{ + "groups": ["apache"], + "description": "HTTP Access Logs: Detect unusual visitor rate (ECS)", + "analysis_config" : { + "bucket_span": "15m", + "summary_count_field_name": "dc_source_address", + "detectors": [ + { + "detector_description": "apache_access_visitor_rate", + "function": "non_zero_count" + } + ], + "influencers": [] + }, + "analysis_limits": { + "model_memory_limit": "10mb" + }, + "data_description": { + "time_field": "@timestamp", + "time_format": "epoch_ms" + }, + "model_plot_config": { + "enabled": true + }, + "custom_settings": { + "created_by": "ml-module-apache-access", + "custom_urls": [ + { + "url_name": "Raw data", + "url_value": "kibana#/discover/ml_http_access_filebeat_ecs?_g=(time:(from:\u0027$earliest$\u0027,mode:absolute,to:\u0027$latest$\u0027))&_a=(columns:!(_source),filters:!((\u0027$state\u0027:(store:appState),meta:(alias:!n,disabled:!f,index:\u0027INDEX_PATTERN_ID\u0027,key:event.dataset,negate:!f,params:(query:\u0027apache.access\u0027,type:phrase),type:phrase,value:\u0027apache.access\u0027),query:(match:(event.dataset:(query:\u0027apache.access\u0027,type:phrase))))),index:\u0027INDEX_PATTERN_ID\u0027,interval:auto,query:(language:lucene,query:\u0027\u0027),sort:!(\u0027@timestamp\u0027,desc))" + } + ] + } +} diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/dashboard/ml_http_access_explorer_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/dashboard/ml_http_access_explorer_ecs.json new file mode 100644 index 0000000000000..681ad572417f6 --- /dev/null +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/dashboard/ml_http_access_explorer_ecs.json @@ -0,0 +1,13 @@ +{ + "hits": 0, + "timeRestore": false, + "description": "", + "title": "ML HTTP Access Explorer (ECS)", + "uiStateJSON": "{\"P-3\":{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":null,\"direction\":null}}}},\"P-5\":{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":null,\"direction\":null}}}}}", + "panelsJSON": "[{\"size_x\":6,\"size_y\":3,\"panelIndex\":1,\"type\":\"visualization\",\"id\":\"ml_http_access_events_timechart_ecs\",\"col\":1,\"row\":1},{\"size_x\":6,\"size_y\":3,\"panelIndex\":2,\"type\":\"visualization\",\"id\":\"ml_http_access_unique_count_url_timechart_ecs\",\"col\":7,\"row\":1},{\"size_x\":6,\"size_y\":3,\"panelIndex\":3,\"type\":\"visualization\",\"id\":\"ml_http_access_status_code_timechart_ecs\",\"col\":1,\"row\":4},{\"size_x\":6,\"size_y\":3,\"panelIndex\":4,\"type\":\"visualization\",\"id\":\"ml_http_access_source_ip_timechart_ecs\",\"col\":7,\"row\":4},{\"size_x\":6,\"size_y\":3,\"panelIndex\":5,\"type\":\"visualization\",\"id\":\"ml_http_access_top_source_ips_table_ecs\",\"col\":1,\"row\":8},{\"size_x\":6,\"size_y\":3,\"panelIndex\":6,\"type\":\"visualization\",\"id\":\"ml_http_access_map_ecs\",\"col\":7,\"row\":8},{\"size_x\":12,\"size_y\":9,\"panelIndex\":7,\"type\":\"visualization\",\"id\":\"ml_http_access_top_urls_table_ecs\",\"col\":1,\"row\":11}]", + "optionsJSON": "{\"darkTheme\":false}", + "version": 1, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\n \"filter\": [],\n \"highlightAll\": true,\n \"version\": true,\n \"query\": {\n \"query\": \"\",\n \"language\": \"lucene\"\n }\n}" + } +} \ No newline at end of file diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/search/ml_http_access_filebeat_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/search/ml_http_access_filebeat_ecs.json new file mode 100644 index 0000000000000..6575c7e65d78d --- /dev/null +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/search/ml_http_access_filebeat_ecs.json @@ -0,0 +1,16 @@ +{ + "sort": [ + "@timestamp", + "desc" + ], + "hits": 0, + "description": "Filebeat HTTP Access Data (ECS)", + "title": "ML HTTP Access Data (ECS)", + "version": 1, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"index\":\"INDEX_PATTERN_ID\",\"query\":{\"query_string\":{\"query\":\"fileset.name:access\",\"analyze_wildcard\":true}},\"filter\":[],\"highlight\":{\"pre_tags\":[\"@kibana-highlighted-field@\"],\"post_tags\":[\"@/kibana-highlighted-field@\"],\"fields\":{\"*\":{}},\"require_field_match\":false,\"fragment_size\":2147483647}}" + }, + "columns": [ + "_source" + ] +} diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_events_timechart_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_events_timechart_ecs.json new file mode 100644 index 0000000000000..ff507da41d1a5 --- /dev/null +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_events_timechart_ecs.json @@ -0,0 +1,11 @@ +{ + "visState": "{\"title\":\"ML HTTP Access Event Timechart (ECS)\",\"type\":\"histogram\",\"params\":{\"shareYAxis\":true,\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"scale\":\"linear\",\"mode\":\"stacked\",\"times\":[],\"addTimeMarker\":false,\"defaultYExtents\":false,\"setYExtents\":false,\"yAxis\":{}},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"date_histogram\",\"schema\":\"segment\",\"params\":{\"field\":\"@timestamp\",\"interval\":\"auto\",\"customInterval\":\"2h\",\"min_doc_count\":1,\"extended_bounds\":{}}},{\"id\":\"3\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"group\",\"params\":{\"field\":\"event.module\",\"size\":10,\"order\":\"desc\",\"orderBy\":\"1\"}},{\"id\":\"4\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"group\",\"params\":{\"field\":\"event.dataset\",\"size\":10,\"order\":\"desc\",\"orderBy\":\"1\"}}],\"listeners\":{}}", + "description": "", + "title": "ML HTTP Access Event Timechart (ECS)", + "uiStateJSON": "{\"vis\":{\"colors\":{\"apache - access\":\"#629E51\",\"nginx - access\":\"#1F78C1\"}}}", + "version": 1, + "savedSearchId": "ml_http_access_filebeat_ecs", + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + } + } \ No newline at end of file diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_map_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_map_ecs.json new file mode 100644 index 0000000000000..4cf3982f5ac6a --- /dev/null +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_map_ecs.json @@ -0,0 +1,11 @@ +{ + "visState": "{\"aggs\":[{\"enabled\":true,\"id\":\"1\",\"params\":{},\"schema\":\"metric\",\"type\":\"count\"},{\"enabled\":true,\"id\":\"2\",\"params\":{\"autoPrecision\":true,\"field\":\"source.geo.location\"},\"schema\":\"segment\",\"type\":\"geohash_grid\"}],\"listeners\":{},\"params\":{\"addTooltip\":true,\"heatBlur\":15,\"heatMaxZoom\":16,\"heatMinOpacity\":0.1,\"heatNormalizeData\":true,\"heatRadius\":25,\"isDesaturated\":true,\"legendPosition\":\"bottomright\",\"mapCenter\":[15,5],\"mapType\":\"Scaled Circle Markers\",\"mapZoom\":2,\"wms\":{\"enabled\":false,\"options\":{\"attribution\":\"Maps provided by USGS\",\"format\":\"image/png\",\"layers\":\"0\",\"styles\":\"\",\"transparent\":true,\"version\":\"1.3.0\"},\"url\":\"https://basemap.nationalmap.gov/arcgis/services/USGSTopo/MapServer/WMSServer\"}},\"title\":\"ML HTTP Access Map (ECS)\",\"type\":\"tile_map\"}", + "description": "", + "title": "ML HTTP Access Map (ECS)", + "uiStateJSON": "{\n \"mapCenter\": [\n 12.039320557540572,\n -0.17578125\n ]\n}", + "version": 1, + "savedSearchId": "ml_http_access_filebeat_ecs", + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + } +} \ No newline at end of file diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_source_ip_timechart_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_source_ip_timechart_ecs.json new file mode 100644 index 0000000000000..8b2a9e14a74b8 --- /dev/null +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_source_ip_timechart_ecs.json @@ -0,0 +1,11 @@ +{ + "visState": "{\"title\":\"ML HTTP Access Source IP Timechart (ECS)\",\"type\":\"area\",\"params\":{\"addLegend\":true,\"addTimeMarker\":false,\"addTooltip\":true,\"categoryAxes\":[{\"id\":\"CategoryAxis-1\",\"labels\":{\"show\":true,\"truncate\":100},\"position\":\"bottom\",\"scale\":{\"type\":\"linear\"},\"show\":true,\"style\":{},\"title\":{\"text\":\"@timestamp per 5 minutes\"},\"type\":\"category\"}],\"defaultYExtents\":false,\"drawLinesBetweenPoints\":true,\"grid\":{\"categoryLines\":false,\"style\":{\"color\":\"#eee\"}},\"interpolate\":\"linear\",\"legendPosition\":\"right\",\"radiusRatio\":9,\"scale\":\"linear\",\"seriesParams\":[{\"data\":{\"id\":\"1\",\"label\":\"Count\"},\"drawLinesBetweenPoints\":true,\"interpolate\":\"linear\",\"mode\":\"stacked\",\"show\":\"true\",\"showCircles\":true,\"type\":\"area\",\"valueAxis\":\"ValueAxis-1\"}],\"setYExtents\":false,\"showCircles\":true,\"times\":[],\"valueAxes\":[{\"id\":\"ValueAxis-1\",\"labels\":{\"filter\":false,\"rotate\":0,\"show\":true,\"truncate\":100},\"name\":\"LeftAxis-1\",\"position\":\"left\",\"scale\":{\"mode\":\"normal\",\"type\":\"linear\"},\"show\":true,\"style\":{},\"title\":{},\"type\":\"value\"}]},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"date_histogram\",\"schema\":\"segment\",\"params\":{\"field\":\"@timestamp\",\"interval\":\"auto\",\"customInterval\":\"2h\",\"min_doc_count\":1,\"extended_bounds\":{}}},{\"id\":\"3\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"group\",\"params\":{\"field\":\"source.address\",\"size\":5,\"order\":\"desc\",\"orderBy\":\"1\"}}],\"listeners\":{}}", + "description": "", + "title": "ML HTTP Access Source IP Timechart (ECS)", + "uiStateJSON": "{}", + "version": 1, + "savedSearchId": "ml_http_access_filebeat_ecs", + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + } +} \ No newline at end of file diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_status_code_timechart_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_status_code_timechart_ecs.json new file mode 100644 index 0000000000000..e104b047f924b --- /dev/null +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_status_code_timechart_ecs.json @@ -0,0 +1,11 @@ +{ + "visState": "{\"title\":\"ML HTTP Access Status Code Timechart (ECS)\",\"type\":\"histogram\",\"params\":{\"shareYAxis\":true,\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"scale\":\"linear\",\"mode\":\"stacked\",\"times\":[],\"addTimeMarker\":false,\"defaultYExtents\":false,\"setYExtents\":false,\"yAxis\":{}},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"date_histogram\",\"schema\":\"segment\",\"params\":{\"field\":\"@timestamp\",\"interval\":\"auto\",\"customInterval\":\"2h\",\"min_doc_count\":1,\"extended_bounds\":{}}},{\"id\":\"3\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"group\",\"params\":{\"field\":\"http.response.status_code\",\"size\":50,\"order\":\"desc\",\"orderBy\":\"1\"}}],\"listeners\":{}}", + "description": "", + "title": "ML HTTP Access Status Code Timechart (ECS)", + "uiStateJSON": "{\n \"vis\": {\n \"colors\": {\n \"200\": \"#7EB26D\",\n \"404\": \"#614D93\"\n }\n }\n}", + "version": 1, + "savedSearchId": "ml_http_access_filebeat_ecs", + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + } +} \ No newline at end of file diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_top_source_ips_table_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_top_source_ips_table_ecs.json new file mode 100644 index 0000000000000..c9afd66ea39b1 --- /dev/null +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_top_source_ips_table_ecs.json @@ -0,0 +1,11 @@ +{ + "visState": "{\"title\":\"ML HTTP Access Top Source IPs Table (ECS)\",\"type\":\"table\",\"params\":{\"perPage\":10,\"showPartialRows\":false,\"showMetricsAtAllLevels\":false,\"sort\":{\"columnIndex\":null,\"direction\":null},\"showTotal\":false,\"totalFunc\":\"sum\"},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"bucket\",\"params\":{\"field\":\"source.address\",\"size\":50,\"order\":\"desc\",\"orderBy\":\"1\"}}],\"listeners\":{}}", + "description": "", + "title": "ML HTTP Access Top Source IPs Table (ECS)", + "uiStateJSON": "{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":null,\"direction\":null}}}}", + "version": 1, + "savedSearchId": "ml_http_access_filebeat_ecs", + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + } +} \ No newline at end of file diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_top_urls_table_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_top_urls_table_ecs.json new file mode 100644 index 0000000000000..34fbcb621085c --- /dev/null +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_top_urls_table_ecs.json @@ -0,0 +1,11 @@ +{ + "visState": "{\"title\":\"ML HTTP Access Top URLs Table (ECS)\",\"type\":\"table\",\"params\":{\"perPage\":100,\"showPartialRows\":false,\"showMetricsAtAllLevels\":false,\"sort\":{\"columnIndex\":null,\"direction\":null},\"showTotal\":false,\"totalFunc\":\"sum\"},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"bucket\",\"params\":{\"field\":\"url.original\",\"size\":1000,\"order\":\"desc\",\"orderBy\":\"1\"}}],\"listeners\":{}}", + "description": "", + "title": "ML HTTP Access Top URLs Table (ECS)", + "uiStateJSON": "{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":null,\"direction\":null}}}}", + "version": 1, + "savedSearchId": "ml_http_access_filebeat_ecs", + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + } +} \ No newline at end of file diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_unique_count_url_timechart_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_unique_count_url_timechart_ecs.json new file mode 100644 index 0000000000000..d14592e68bb6e --- /dev/null +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_unique_count_url_timechart_ecs.json @@ -0,0 +1,11 @@ +{ + "visState": "{\"title\":\"ML HTTP Access Unique Count URL Timechart (ECS)\",\"type\":\"line\",\"params\":{\"grid\":{\"categoryLines\":false,\"style\":{\"color\":\"#eee\"}},\"categoryAxes\":[{\"id\":\"CategoryAxis-1\",\"type\":\"category\",\"position\":\"bottom\",\"show\":true,\"style\":{},\"scale\":{\"type\":\"linear\"},\"labels\":{\"show\":true,\"truncate\":100},\"title\":{\"text\":\"@timestamp per day\"}}],\"valueAxes\":[{\"id\":\"ValueAxis-1\",\"name\":\"LeftAxis-1\",\"type\":\"value\",\"position\":\"left\",\"show\":true,\"style\":{},\"scale\":{\"type\":\"linear\",\"mode\":\"normal\"},\"labels\":{\"show\":true,\"rotate\":0,\"filter\":false,\"truncate\":100},\"title\":{\"text\":\"Unique count of url.original\"}}],\"seriesParams\":[{\"show\":true,\"mode\":\"normal\",\"type\":\"line\",\"drawLinesBetweenPoints\":true,\"showCircles\":true,\"interpolate\":\"linear\",\"lineWidth\":2,\"data\":{\"id\":\"1\",\"label\":\"Unique count of url.original\"},\"valueAxis\":\"ValueAxis-1\"}],\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"showCircles\":true,\"interpolate\":\"linear\",\"scale\":\"linear\",\"drawLinesBetweenPoints\":true,\"radiusRatio\":9,\"times\":[],\"addTimeMarker\":false,\"defaultYExtents\":false,\"setYExtents\":false},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"cardinality\",\"schema\":\"metric\",\"params\":{\"field\":\"url.original\"}},{\"id\":\"2\",\"enabled\":true,\"type\":\"date_histogram\",\"schema\":\"segment\",\"params\":{\"field\":\"@timestamp\",\"interval\":\"auto\",\"customInterval\":\"2h\",\"min_doc_count\":1,\"extended_bounds\":{}}}],\"listeners\":{}}", + "description": "", + "title": "ML HTTP Access Unique Count URL Timechart (ECS)", + "uiStateJSON": "{}", + "version": 1, + "savedSearchId": "ml_http_access_filebeat_ecs", + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + } +} \ No newline at end of file diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/logo.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/logo.json new file mode 100644 index 0000000000000..9372c36cbfa6d --- /dev/null +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/logo.json @@ -0,0 +1,5 @@ +{ + "src": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAlCAMAAAAUaRt1AAACPVBMVEUAcQAAdgAAfQAAfgAAfwAAgAAAggAAhAAAhQAAhgAAhwIAhwUAiAcAiAoAiQkAiQwAiQ8Aig4AihEAixMAixUAixcAjBYAjBcAjBkAjRgAjRoAjRwAjh0Ajh8Ajx8AjyAAkCIAkCMAkSMAkSUAkSYAkiYAkicAkigAkikAkykAkyoAkywAlCsAlC0AlS4AlTAAli8AljEAljIAlzIAlzMAlzUAmDQAmDYAmDcAmTcAmTgAmToAmTsAmjgAmjkAmjsEmToLmz0TnD4TnEAYnEAanUEcnkMfnkMhn0Qhn0UnoUcroksvo0svo0w4pVI+qFY+qFdEq1xFq1xIrF5KrF9KrWFLrWBNrWFNrmJPr2NTsWhUsGdVsWdXsWlYsmtZs2xbtW5puXhvvH1vvH5zv4N2v4R3wId3wYd6w4p/w4uAw4uAxIyBxIyBxY6CxY6ExY+FxpCHyJSJyZaMypmOy5uQypmRy5uSzJyUzZ2b0aed0aig06yj06ul1K2p1rCq1rGs2LWu2reu2rix2rex3Ly84MS94MO+4MO+4cS+4ca/4sfB4cbC48nJ5c3L5s7L5s/M59DM6NLN6NLO6NLO6dXQ6tbR69jU69bY7t7Z7t3Z7t7a79/b8OHc8OHe8OLf8ePh8OTh8uXm9Oro8+no9Ovp9Orp9ezq9ezq9u3r9u3s9e3s9e/u9e/u9/Hv+PPw9/Lx+PPy+fTy+fXz+fXz+vb0+vb3+/n3/Pn4/Pr5+/n6/Pr9/v3+/v7+//7///+8pzoSAAACRElEQVR42nXU91/TQBQA8CiUpli4po22jaVNRevosulLzgQXuAruiVi3uPceqKgI4sK6994b9wD927ykkRJI3g/Ju8v3k1zu7h311xCPly58YOwxgE/7AiGe29NpBU6nXQmME+74yW4zcGMBimAggSNo3rV+4FUjG5BE0EKUgp51zwzgd1Mlm5KgJySB5Y/8KIALdWgMBkPgKJpxVgf3Vrt4LMbjUAgxERdxmFl5h4DP2/1+kIVSjqMjiv58opfl0DgM3JCNX6hZZUkMac/ajovbOC4vJO/M8x1ZBoM0FtVSUfXjzqz6uTZHhayCmO82aVUHSCpHqIxA7iUHtBE327Sxhmp+kcaKQSqemgf2Xfmf3lGUFgH48e9IXo96g536tCy3yQRUvSHpElPQPc2uWIIrr8nleRApVuDQdPUdOTo4wgKcodar4nixb5I5aC8deEwVmwdUvTUFbYOH0pdUMafkozlwiyjwlCTfaq9agORk+5Qukr18ZAVAsTVo82EJRCjaqoNFqO9atBMAUrS42fiGwmrmGAJArqBzGsjqq6ktcFk96TlIqxYUxN8krfdCOL8fZjuThHjWtOwfPkrfcO6RW1qO1rEy4FR5hvq6yceBDE6nY/T/LalU0k6Hb4Ikct4NnWRX313FhDGk0tArUoKIh7mW3dLr4lzGFe1bFzFXTWuhsn428axgrKzQ4e+G2nzR6DHW5pN+1X19fk91l8+9bHY+/DmVYhIYJ92xE11WJ8xeLsT7d3+wOmFIPGxYfN/Y8w+Jrert1P9yBgAAAABJRU5ErkJggg==", + "height": 25, + "width": 120 +} diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/manifest.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/manifest.json new file mode 100644 index 0000000000000..bd31fcd23f504 --- /dev/null +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/manifest.json @@ -0,0 +1,111 @@ +{ + "id": "nginx_ecs", + "title": "Nginx access logs", + "description": "Find unusual activity in HTTP access logs from filebeat (ECS)", + "type": "Web Access Logs", + "logoFile": "logo.json", + "defaultIndexPattern": "filebeat-*", + "query": { + "bool": { + "filter": [ + { "term": { "event.dataset": "nginx.access" } }, + { "exists": { "field": "source.address" } }, + { "exists": { "field": "url.original" } }, + { "exists": { "field": "http.response.status_code" } } + ] + } + }, + "jobs": [ + { + "id": "visitor_rate_ecs", + "file": "visitor_rate_ecs.json" + }, + { + "id": "status_code_rate_ecs", + "file": "status_code_rate_ecs.json" + }, + { + "id": "source_ip_url_count_ecs", + "file": "source_ip_url_count_ecs.json" + }, + { + "id": "source_ip_request_rate_ecs", + "file": "source_ip_request_rate_ecs.json" + }, + { + "id": "low_request_rate_ecs", + "file": "low_request_rate_ecs.json" + } + ], + "datafeeds": [ + { + "id": "datafeed-visitor_rate_ecs", + "file": "datafeed_visitor_rate_ecs.json", + "job_id": "visitor_rate_ecs" + }, + { + "id": "datafeed-status_code_rate_ecs", + "file": "datafeed_status_code_rate_ecs.json", + "job_id": "status_code_rate_ecs" + }, + { + "id": "datafeed-source_ip_url_count_ecs", + "file": "datafeed_source_ip_url_count_ecs.json", + "job_id": "source_ip_url_count_ecs" + }, + { + "id": "datafeed-source_ip_request_rate_ecs", + "file": "datafeed_source_ip_request_rate_ecs.json", + "job_id": "source_ip_request_rate_ecs" + }, + { + "id": "datafeed-low_request_rate_ecs", + "file": "datafeed_low_request_rate_ecs.json", + "job_id": "low_request_rate_ecs" + } + ], + "kibana": { + "dashboard": [ + { + "id": "ml_http_access_explorer_ecs", + "file": "ml_http_access_explorer_ecs.json" + } + ], + "search": [ + { + "id": "ml_http_access_filebeat_ecs", + "file": "ml_http_access_filebeat_ecs.json" + } + ], + "visualization": [ + { + "id": "ml_http_access_map_ecs", + "file": "ml_http_access_map_ecs.json" + }, + { + "id": "ml_http_access_source_ip_timechart_ecs", + "file": "ml_http_access_source_ip_timechart_ecs.json" + }, + { + "id": "ml_http_access_status_code_timechart_ecs", + "file": "ml_http_access_status_code_timechart_ecs.json" + }, + { + "id": "ml_http_access_top_source_ips_table_ecs", + "file": "ml_http_access_top_source_ips_table_ecs.json" + }, + { + "id": "ml_http_access_top_urls_table_ecs", + "file": "ml_http_access_top_urls_table_ecs.json" + }, + { + "id": "ml_http_access_unique_count_url_timechart_ecs", + "file": "ml_http_access_unique_count_url_timechart_ecs.json" + }, + { + "id": "ml_http_access_events_timechart_ecs", + "file": "ml_http_access_events_timechart_ecs.json" + } + ] + } +} diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_low_request_rate_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_low_request_rate_ecs.json new file mode 100644 index 0000000000000..cf143071aa519 --- /dev/null +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_low_request_rate_ecs.json @@ -0,0 +1,34 @@ +{ + "job_id": "JOB_ID", + "indexes": [ + "INDEX_PATTERN_NAME" + ], + "query": { + "bool": { + "filter": [ + { "term": { "event.dataset": "nginx.access" } } + ] + } + }, + "aggregations": { + "buckets": { + "date_histogram": { + "field": "@timestamp", + "interval": 900000, + "offset": 0, + "order": { + "_key": "asc" + }, + "keyed": false, + "min_doc_count": 0 + }, + "aggregations": { + "@timestamp": { + "max": { + "field": "@timestamp" + } + } + } + } + } +} diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_source_ip_request_rate_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_source_ip_request_rate_ecs.json new file mode 100644 index 0000000000000..bccf1bd8de6d5 --- /dev/null +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_source_ip_request_rate_ecs.json @@ -0,0 +1,13 @@ +{ + "job_id": "JOB_ID", + "indexes": [ + "INDEX_PATTERN_NAME" + ], + "query": { + "bool": { + "filter": [ + { "term": { "event.dataset": "nginx.access" } } + ] + } + } +} diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_source_ip_url_count_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_source_ip_url_count_ecs.json new file mode 100644 index 0000000000000..bccf1bd8de6d5 --- /dev/null +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_source_ip_url_count_ecs.json @@ -0,0 +1,13 @@ +{ + "job_id": "JOB_ID", + "indexes": [ + "INDEX_PATTERN_NAME" + ], + "query": { + "bool": { + "filter": [ + { "term": { "event.dataset": "nginx.access" } } + ] + } + } +} diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_status_code_rate_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_status_code_rate_ecs.json new file mode 100644 index 0000000000000..bccf1bd8de6d5 --- /dev/null +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_status_code_rate_ecs.json @@ -0,0 +1,13 @@ +{ + "job_id": "JOB_ID", + "indexes": [ + "INDEX_PATTERN_NAME" + ], + "query": { + "bool": { + "filter": [ + { "term": { "event.dataset": "nginx.access" } } + ] + } + } +} diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_visitor_rate_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_visitor_rate_ecs.json new file mode 100644 index 0000000000000..297bd16db4edf --- /dev/null +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_visitor_rate_ecs.json @@ -0,0 +1,39 @@ +{ + "job_id": "JOB_ID", + "indexes": [ + "INDEX_PATTERN_NAME" + ], + "query": { + "bool": { + "filter": [ + { "term": { "event.dataset": "nginx.access" } } + ] + } + }, + "aggregations": { + "buckets": { + "date_histogram": { + "field": "@timestamp", + "interval": 900000, + "offset": 0, + "order": { + "_key": "asc" + }, + "keyed": false, + "min_doc_count": 0 + }, + "aggregations": { + "@timestamp": { + "max": { + "field": "@timestamp" + } + }, + "dc_source_address": { + "cardinality": { + "field": "source.address" + } + } + } + } + } +} diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/low_request_rate_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/low_request_rate_ecs.json new file mode 100644 index 0000000000000..22631813346dc --- /dev/null +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/low_request_rate_ecs.json @@ -0,0 +1,34 @@ +{ + "groups": ["nginx"], + "description": "HTTP Access Logs: Detect low request rate (ECS)", + "analysis_config" : { + "bucket_span": "15m", + "summary_count_field_name": "doc_count", + "detectors": [ + { + "detector_description": "nginx_access_low_request_rate", + "function": "low_count" + } + ], + "influencers": [] + }, + "analysis_limits": { + "model_memory_limit": "10mb" + }, + "data_description": { + "time_field": "@timestamp", + "time_format": "epoch_ms" + }, + "model_plot_config": { + "enabled": true + }, + "custom_settings": { + "created_by": "ml-module-nginx-access", + "custom_urls": [ + { + "url_name": "Raw data", + "url_value": "kibana#/discover/ml_http_access_filebeat_ecs?_g=(time:(from:\u0027$earliest$\u0027,mode:absolute,to:\u0027$latest$\u0027))&_a=(columns:!(_source),filters:!((\u0027$state\u0027:(store:appState),meta:(alias:!n,disabled:!f,index:\u0027INDEX_PATTERN_ID\u0027,key:event.dataset,negate:!f,params:(query:\u0027nginx.access\u0027,type:phrase),type:phrase,value:\u0027nginx.access\u0027),query:(match:(event.dataset:(query:\u0027nginx.access\u0027,type:phrase))))),index:\u0027INDEX_PATTERN_ID\u0027,interval:auto,query:(language:lucene,query:\u0027\u0027),sort:!(\u0027@timestamp\u0027,desc))" + } + ] + } +} diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/source_ip_request_rate_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/source_ip_request_rate_ecs.json new file mode 100644 index 0000000000000..7d91ec8a81cd2 --- /dev/null +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/source_ip_request_rate_ecs.json @@ -0,0 +1,34 @@ +{ + "groups": ["nginx"], + "description": "HTTP Access Logs: Detect unusual source ips - high request rates (ECS)", + "analysis_config" : { + "bucket_span": "1h", + "detectors": [ + { + "detector_description": "nginx_access_source_ip_high_count", + "function": "high_count", + "over_field_name": "source.address" + } + ], + "influencers": [ + "source.address" + ] + }, + "data_description": { + "time_field": "@timestamp", + "time_format": "epoch_ms" + }, + "custom_settings": { + "created_by": "ml-module-nginx-access", + "custom_urls": [ + { + "url_name": "Investigate source IP", + "url_value": "kibana#/dashboard/ml_http_access_explorer_ecs?_g=(time:(from:\u0027$earliest$\u0027,mode:absolute,to:\u0027$latest$\u0027))&_a=(description:\u0027\u0027,filters:!((\u0027$state\u0027:(store:appState),meta:(alias:!n,disabled:!f,index:\u0027INDEX_PATTERN_ID\u0027,key:event.dataset,negate:!f,params:(query:\u0027nginx.access\u0027,type:phrase),type:phrase,value:\u0027nginx.access\u0027),query:(match:(event.dataset:(query:\u0027nginx.access\u0027,type:phrase)))),(\u0027$state\u0027:(store:appState),meta:(alias:!n,disabled:!f,index:\u0027INDEX_PATTERN_ID\u0027,key:source.address,negate:!f,type:phrase,value:\u0027$source.address$\u0027),query:(match:(source.address:(query:\u0027$source.address$\u0027,type:phrase))))),query:(language:lucene,query:\u0027\u0027))" + }, + { + "url_name": "Raw data", + "url_value": "kibana#/discover/ml_http_access_filebeat_ecs?_g=(time:(from:\u0027$earliest$\u0027,mode:absolute,to:\u0027$latest$\u0027))&_a=(columns:!(_source),filters:!((\u0027$state\u0027:(store:appState),meta:(alias:!n,disabled:!f,index:\u0027INDEX_PATTERN_ID\u0027,key:event.dataset,negate:!f,params:(query:\u0027nginx.access\u0027,type:phrase),type:phrase,value:\u0027nginx.access\u0027),query:(match:(event.dataset:(query:\u0027nginx.access\u0027,type:phrase)))),(\u0027$state\u0027:(store:appState),meta:(alias:!n,disabled:!f,index:\u0027INDEX_PATTERN_ID\u0027,key:source.address,negate:!f,type:phrase,value:\u0027$source.address$\u0027),query:(match:(source.address:(query:\u0027$source.address$\u0027,type:phrase))))),index:\u0027INDEX_PATTERN_ID\u0027,interval:auto,query:(language:lucene,query:\u0027\u0027),sort:!(\u0027@timestamp\u0027,desc))" + } + ] + } +} diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/source_ip_url_count_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/source_ip_url_count_ecs.json new file mode 100644 index 0000000000000..dc965e7061b95 --- /dev/null +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/source_ip_url_count_ecs.json @@ -0,0 +1,35 @@ +{ + "groups": ["nginx"], + "description": "HTTP Access Logs: Detect unusual source ips - high distinct count of urls (ECS)", + "analysis_config" : { + "bucket_span": "1h", + "detectors": [ + { + "detector_description": "nginx_access_source_ip_high_dc_url", + "function": "high_distinct_count", + "field_name": "url.original", + "over_field_name": "source.address" + } + ], + "influencers": [ + "source.address" + ] + }, + "data_description": { + "time_field": "@timestamp", + "time_format": "epoch_ms" + }, + "custom_settings": { + "created_by": "ml-module-nginx-access", + "custom_urls": [ + { + "url_name": "Investigate source IP", + "url_value": "kibana#/dashboard/ml_http_access_explorer_ecs?_g=(time:(from:\u0027$earliest$\u0027,mode:absolute,to:\u0027$latest$\u0027))&_a=(description:\u0027\u0027,filters:!((\u0027$state\u0027:(store:appState),meta:(alias:!n,disabled:!f,index:\u0027INDEX_PATTERN_ID\u0027,key:event.dataset,negate:!f,params:(query:\u0027nginx.access\u0027,type:phrase),type:phrase,value:\u0027nginx.access\u0027),query:(match:(event.dataset:(query:\u0027nginx.access\u0027,type:phrase)))),(\u0027$state\u0027:(store:appState),meta:(alias:!n,disabled:!f,index:\u0027INDEX_PATTERN_ID\u0027,key:source.address,negate:!f,type:phrase,value:\u0027$source.address$\u0027),query:(match:(source.address:(query:\u0027$source.address$\u0027,type:phrase))))),query:(language:lucene,query:\u0027\u0027))" + }, + { + "url_name": "Raw data", + "url_value": "kibana#/discover/ml_http_access_filebeat_ecs?_g=(time:(from:\u0027$earliest$\u0027,mode:absolute,to:\u0027$latest$\u0027))&_a=(columns:!(_source),filters:!((\u0027$state\u0027:(store:appState),meta:(alias:!n,disabled:!f,index:\u0027INDEX_PATTERN_ID\u0027,key:event.dataset,negate:!f,params:(query:\u0027nginx.access\u0027,type:phrase),type:phrase,value:\u0027nginx.access\u0027),query:(match:(event.dataset:(query:\u0027nginx.access\u0027,type:phrase)))),(\u0027$state\u0027:(store:appState),meta:(alias:!n,disabled:!f,index:\u0027INDEX_PATTERN_ID\u0027,key:source.address,negate:!f,type:phrase,value:\u0027$source.address$\u0027),query:(match:(source.address:(query:\u0027$source.address$\u0027,type:phrase))))),index:\u0027INDEX_PATTERN_ID\u0027,interval:auto,query:(language:lucene,query:\u0027\u0027),sort:!(\u0027@timestamp\u0027,desc))" + } + ] + } +} diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/status_code_rate_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/status_code_rate_ecs.json new file mode 100644 index 0000000000000..14cba15dd14f4 --- /dev/null +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/status_code_rate_ecs.json @@ -0,0 +1,41 @@ +{ + "groups": ["nginx"], + "description": "HTTP Access Logs: Detect unusual status_code rates (ECS)", + "analysis_config" : { + "bucket_span": "15m", + "detectors": [ + { + "detector_description": "nginx_access_status_code_rate", + "function": "count", + "partition_field_name": "http.response.status_code" + } + ], + "influencers": [ + "http.response.status_code", + "source.address" + ] + }, + "analysis_limits": { + "model_memory_limit": "100mb" + }, + "data_description": { + "time_field": "@timestamp", + "time_format": "epoch_ms" + }, + "model_plot_config": { + "enabled": true + }, + "custom_settings": { + "created_by": "ml-module-nginx-access", + "custom_urls": [ + { + "url_name": "Investigate status code", + "url_value": "kibana#/dashboard/ml_http_access_explorer_ecs?_g=(time:(from:\u0027$earliest$\u0027,mode:absolute,to:\u0027$latest$\u0027))&_a=(description:\u0027\u0027,filters:!((\u0027$state\u0027:(store:appState),meta:(alias:!n,disabled:!f,index:\u0027INDEX_PATTERN_ID\u0027,key:event.dataset,negate:!f,params:(query:\u0027nginx.access\u0027,type:phrase),type:phrase,value:\u0027nginx.access\u0027),query:(match:(event.dataset:(query:\u0027nginx.access\u0027,type:phrase)))),(\u0027$state\u0027:(store:appState),meta:(alias:!n,disabled:!f,index:\u0027INDEX_PATTERN_ID\u0027,key:http.response.status_code,negate:!f,params:(query:\u0027$http.response.status_code$\u0027,type:phrase),type:phrase,value:\u0027$http.response.status_code$\u0027),query:(match:(http.response.status_code:(query:\u0027$http.response.status_code$\u0027,type:phrase))))),query:(language:lucene,query:\u0027\u0027))" + }, + { + "url_name": "Raw data", + "url_value": "kibana#/discover/ml_http_access_filebeat_ecs?_g=(time:(from:\u0027$earliest$\u0027,mode:absolute,to:\u0027$latest$\u0027))&_a=(columns:!(_source),filters:!((\u0027$state\u0027:(store:appState),meta:(alias:!n,disabled:!f,index:\u0027INDEX_PATTERN_ID\u0027,key:event.dataset,negate:!f,params:(query:\u0027nginx.access\u0027,type:phrase),type:phrase,value:\u0027nginx.access\u0027),query:(match:(event.dataset:(query:\u0027nginx.access\u0027,type:phrase)))),(\u0027$state\u0027:(store:appState),meta:(alias:!n,disabled:!f,index:\u0027INDEX_PATTERN_ID\u0027,key:http.response.status_code,negate:!f,type:phrase,value:\u0027$http.response.status_code$\u0027),query:(match:(http.response.status_code:(query:\u0027$http.response.status_code$\u0027,type:phrase))))),index:\u0027INDEX_PATTERN_ID\u0027,interval:auto,query:(language:lucene,query:\u0027\u0027),sort:!(\u0027@timestamp\u0027,desc))" + } + ] + } +} \ No newline at end of file diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/visitor_rate_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/visitor_rate_ecs.json new file mode 100644 index 0000000000000..e0ba814facb8d --- /dev/null +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/visitor_rate_ecs.json @@ -0,0 +1,34 @@ +{ + "groups": ["nginx"], + "description": "HTTP Access Logs: Detect unusual visitor rate (ECS)", + "analysis_config" : { + "bucket_span": "15m", + "summary_count_field_name": "dc_source_address", + "detectors": [ + { + "detector_description": "nginx_access_visitor_rate", + "function": "non_zero_count" + } + ], + "influencers": [] + }, + "analysis_limits": { + "model_memory_limit": "10mb" + }, + "data_description": { + "time_field": "@timestamp", + "time_format": "epoch_ms" + }, + "model_plot_config": { + "enabled": true + }, + "custom_settings": { + "created_by": "ml-module-nginx-access", + "custom_urls": [ + { + "url_name": "Raw data", + "url_value": "kibana#/discover/ml_http_access_filebeat_ecs?_g=(time:(from:\u0027$earliest$\u0027,mode:absolute,to:\u0027$latest$\u0027))&_a=(columns:!(_source),filters:!((\u0027$state\u0027:(store:appState),meta:(alias:!n,disabled:!f,index:\u0027INDEX_PATTERN_ID\u0027,key:event.dataset,negate:!f,params:(query:\u0027nginx.access\u0027,type:phrase),type:phrase,value:\u0027nginx.access\u0027),query:(match:(event.dataset:(query:\u0027nginx.access\u0027,type:phrase))))),index:\u0027INDEX_PATTERN_ID\u0027,interval:auto,query:(language:lucene,query:\u0027\u0027),sort:!(\u0027@timestamp\u0027,desc))" + } + ] + } +} diff --git a/x-pack/plugins/monitoring/common/constants.js b/x-pack/plugins/monitoring/common/constants.js index 372f0bd492d24..d87bb2c8be8fe 100644 --- a/x-pack/plugins/monitoring/common/constants.js +++ b/x-pack/plugins/monitoring/common/constants.js @@ -152,3 +152,10 @@ export const DEBOUNCE_FAST_MS = 10; // roughly how long it takes to render a fra export const CLUSTER_ALERTS_ADDRESS_CONFIG_KEY = 'cluster_alerts.email_notifications.email_address'; export const STANDALONE_CLUSTER_CLUSTER_UUID = '__standalone_cluster__'; + +export const INDEX_PATTERN = '.monitoring-*-6-*'; +export const INDEX_PATTERN_KIBANA = '.monitoring-kibana-6-*'; +export const INDEX_PATTERN_LOGSTASH = '.monitoring-logstash-6-*'; +export const INDEX_PATTERN_BEATS = '.monitoring-beats-6-*'; +export const INDEX_ALERTS = '.monitoring-alerts-6'; +export const INDEX_PATTERN_ELASTICSEARCH = '.monitoring-es-6-*'; diff --git a/x-pack/plugins/monitoring/config.js b/x-pack/plugins/monitoring/config.js index 6074b8c34b232..0775d219c0238 100644 --- a/x-pack/plugins/monitoring/config.js +++ b/x-pack/plugins/monitoring/config.js @@ -30,23 +30,14 @@ export const config = (Joi) => { }).default() }).default() }).default(), - index_pattern: Joi.string().default('.monitoring-*-6-*'), kibana: Joi.object({ - index_pattern: Joi.string().default('.monitoring-kibana-6-*'), collection: Joi.object({ enabled: Joi.boolean().default(true), interval: Joi.number().default(10000) // op status metrics get buffered at `ops.interval` and flushed to the bulk endpoint at this interval }).default() }).default(), - logstash: Joi.object({ - index_pattern: Joi.string().default('.monitoring-logstash-6-*') - }).default(), - beats: Joi.object({ - index_pattern: Joi.string().default('.monitoring-beats-6-*') - }).default(), cluster_alerts: Joi.object({ enabled: Joi.boolean().default(true), - index: Joi.string().default('.monitoring-alerts-6'), email_notifications: Joi.object({ enabled: Joi.boolean().default(true), email_address: Joi.string().email(), @@ -62,7 +53,6 @@ export const config = (Joi) => { }).default(), elasticsearch: Joi.object({ customHeaders: Joi.object().default({}), - index_pattern: Joi.string().default('.monitoring-es-6-*'), logQueries: Joi.boolean().default(false), requestHeadersWhitelist: Joi.array().items().single().default(DEFAULT_REQUEST_HEADERS), sniffOnStart: Joi.boolean().default(false), diff --git a/x-pack/plugins/monitoring/public/components/logstash/pipeline_listing/pipeline_listing.js b/x-pack/plugins/monitoring/public/components/logstash/pipeline_listing/pipeline_listing.js index 99dc8afadaf12..73a78434e7116 100644 --- a/x-pack/plugins/monitoring/public/components/logstash/pipeline_listing/pipeline_listing.js +++ b/x-pack/plugins/monitoring/public/components/logstash/pipeline_listing/pipeline_listing.js @@ -16,8 +16,8 @@ import { injectI18n } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; class PipelineListingUI extends Component { - tooltipXValueFormatter(xValue) { - return moment(xValue).format(this.props.dateFormat); + tooltipXValueFormatter(xValue, dateFormat) { + return moment(xValue).format(dateFormat); } tooltipYValueFormatter(yValue, format, units) { @@ -25,7 +25,7 @@ class PipelineListingUI extends Component { } getColumns() { - const { onBrush } = this.props; + const { onBrush, dateFormat } = this.props; const { kbnUrl, scope } = this.props.angular; return [ @@ -66,7 +66,7 @@ class PipelineListingUI extends Component { series={throughput.data} onBrush={onBrush} tooltip={{ - xValueFormatter: value => this.tooltipXValueFormatter(value), + xValueFormatter: value => this.tooltipXValueFormatter(value, dateFormat), yValueFormatter: partialRight(this.tooltipYValueFormatter, throughput.metric.format, throughput.metric.units) }} options={{ xaxis: throughput.timeRange }} @@ -100,7 +100,7 @@ class PipelineListingUI extends Component { series={nodesCount.data} onBrush={onBrush} tooltip={{ - xValueFormatter: this.tooltipXValueFormatter, + xValueFormatter: value => this.tooltipXValueFormatter(value, dateFormat), yValueFormatter: partialRight(this.tooltipYValueFormatter, nodesCount.metric.format, nodesCount.metric.units) }} options={{ xaxis: nodesCount.timeRange }} diff --git a/x-pack/plugins/monitoring/public/services/title.js b/x-pack/plugins/monitoring/public/services/title.js index 21e994ce970a2..15e9b0a6c8c4c 100644 --- a/x-pack/plugins/monitoring/public/services/title.js +++ b/x-pack/plugins/monitoring/public/services/title.js @@ -16,8 +16,8 @@ uiModule.service('title', (Private, i18n) => { clusterName = (clusterName) ? `- ${clusterName}` : ''; suffix = (suffix) ? `- ${suffix}` : ''; docTitle.change( - i18n('xpack.monitoring.monitoringDocTitle', { - defaultMessage: 'Monitoring {clusterName} {suffix}', + i18n('xpack.monitoring.stackMonitoringDocTitle', { + defaultMessage: 'Stack Monitoring {clusterName} {suffix}', values: { clusterName, suffix } }), true); }; diff --git a/x-pack/plugins/monitoring/public/views/logstash/node/pipelines/index.js b/x-pack/plugins/monitoring/public/views/logstash/node/pipelines/index.js index d4ee051772899..f3adea3ecb75a 100644 --- a/x-pack/plugins/monitoring/public/views/logstash/node/pipelines/index.js +++ b/x-pack/plugins/monitoring/public/views/logstash/node/pipelines/index.js @@ -72,6 +72,7 @@ uiRoutes controller: class extends MonitoringViewBaseEuiTableController { constructor($injector, $scope, i18n) { const kbnUrl = $injector.get('kbnUrl'); + const config = $injector.get('config'); super({ defaultData: {}, @@ -103,6 +104,7 @@ uiRoutes sorting={this.sorting} pagination={this.pagination} onTableChange={this.onTableChange} + dateFormat={config.get('dateFormat')} upgradeMessage={makeUpgradeMessage(data.nodeSummary.version, i18n)} angular={{ kbnUrl, diff --git a/x-pack/plugins/monitoring/public/views/logstash/pipelines/index.js b/x-pack/plugins/monitoring/public/views/logstash/pipelines/index.js index 1e88cc678b904..d800e565ba502 100644 --- a/x-pack/plugins/monitoring/public/views/logstash/pipelines/index.js +++ b/x-pack/plugins/monitoring/public/views/logstash/pipelines/index.js @@ -78,6 +78,7 @@ uiRoutes const $route = $injector.get('$route'); const kbnUrl = $injector.get('kbnUrl'); + const config = $injector.get('config'); this.data = $route.current.locals.pageData; const globalState = $injector.get('globalState'); $scope.cluster = find($route.current.locals.clusters, { cluster_uuid: globalState.cluster_uuid }); @@ -110,6 +111,7 @@ uiRoutes pagination={this.pagination} onTableChange={this.onTableChange} upgradeMessage={upgradeMessage} + dateFormat={config.get('dateFormat')} angular={{ kbnUrl, scope: $scope, diff --git a/x-pack/plugins/monitoring/server/lib/__tests__/ccs_utils.js b/x-pack/plugins/monitoring/server/lib/__tests__/ccs_utils.js index 8b2ae13803782..e2aa07ad78ee1 100644 --- a/x-pack/plugins/monitoring/server/lib/__tests__/ccs_utils.js +++ b/x-pack/plugins/monitoring/server/lib/__tests__/ccs_utils.js @@ -10,76 +10,65 @@ import { parseCrossClusterPrefix, prefixIndexPattern } from '../ccs_utils'; describe('ccs_utils', () => { describe('prefixIndexPattern', () => { - const indexPatternName = 'xyz'; const indexPattern = '.monitoring-xyz-1-*,.monitoring-xyz-2-*'; it('returns the index pattern if ccs is not enabled', () => { const get = sinon.stub(); const config = { get }; - get.withArgs(indexPatternName).returns(indexPattern); get.withArgs('xpack.monitoring.ccs.enabled').returns(false); // falsy string values should be ignored - const allPattern = prefixIndexPattern(config, indexPatternName, '*'); - const onePattern = prefixIndexPattern(config, indexPatternName, 'do_not_use_me'); + const allPattern = prefixIndexPattern(config, indexPattern, '*'); + const onePattern = prefixIndexPattern(config, indexPattern, 'do_not_use_me'); expect(allPattern).to.be(indexPattern); expect(onePattern).to.be(indexPattern); - // uses the config and indexPatternName supplied - expect(get.callCount).to.eql(4); + expect(get.callCount).to.eql(2); }); it('returns the index pattern if ccs is not used', () => { const get = sinon.stub(); const config = { get }; - get.withArgs(indexPatternName).returns(indexPattern); get.withArgs('xpack.monitoring.ccs.enabled').returns(true); // falsy string values should be ignored - const undefinedPattern = prefixIndexPattern(config, indexPatternName); - const nullPattern = prefixIndexPattern(config, indexPatternName, null); - const blankPattern = prefixIndexPattern(config, indexPatternName, ''); + const undefinedPattern = prefixIndexPattern(config, indexPattern); + const nullPattern = prefixIndexPattern(config, indexPattern, null); + const blankPattern = prefixIndexPattern(config, indexPattern, ''); expect(undefinedPattern).to.be(indexPattern); expect(nullPattern).to.be(indexPattern); expect(blankPattern).to.be(indexPattern); - // uses the config and indexPatternName supplied - expect(get.callCount).to.eql(6); + expect(get.callCount).to.eql(3); }); it('returns the ccs-prefixed index pattern', () => { const get = sinon.stub(); const config = { get }; - get.withArgs(indexPatternName).returns(indexPattern); get.withArgs('xpack.monitoring.ccs.enabled').returns(true); - const abcPattern = prefixIndexPattern(config, indexPatternName, 'aBc'); - const underscorePattern = prefixIndexPattern(config, indexPatternName, 'cluster_one'); + const abcPattern = prefixIndexPattern(config, indexPattern, 'aBc'); + const underscorePattern = prefixIndexPattern(config, indexPattern, 'cluster_one'); expect(abcPattern).to.eql('aBc:.monitoring-xyz-1-*,aBc:.monitoring-xyz-2-*'); expect(underscorePattern).to.eql('cluster_one:.monitoring-xyz-1-*,cluster_one:.monitoring-xyz-2-*'); - - // uses the config and indexPatternName supplied, but only calls once - expect(get.callCount).to.eql(4); + expect(get.callCount).to.eql(2); }); it('returns the ccs-prefixed index pattern when wildcard and the local cluster pattern', () => { const get = sinon.stub(); const config = { get }; - get.withArgs(indexPatternName).returns(indexPattern); get.withArgs('xpack.monitoring.ccs.enabled').returns(true); - const pattern = prefixIndexPattern(config, indexPatternName, '*'); + const pattern = prefixIndexPattern(config, indexPattern, '*'); // it should have BOTH patterns so that it searches all CCS clusters and the local cluster expect(pattern).to.eql('*:.monitoring-xyz-1-*,*:.monitoring-xyz-2-*' + ',' + indexPattern); - - // uses the config and indexPatternName supplied, but only calls once - expect(get.callCount).to.eql(2); + expect(get.callCount).to.eql(1); }); }); diff --git a/x-pack/plugins/monitoring/server/lib/ccs_utils.js b/x-pack/plugins/monitoring/server/lib/ccs_utils.js index d879a8d51c100..30409960edd9e 100644 --- a/x-pack/plugins/monitoring/server/lib/ccs_utils.js +++ b/x-pack/plugins/monitoring/server/lib/ccs_utils.js @@ -11,13 +11,12 @@ * which means that the index pattern will be returned without using {@code ccs}. * * @param {Object} config The Kibana configuration object. - * @param {String} indexPatternName The index pattern name (e.g., 'xpack.monitoring.elasticsearch.index_pattern') + * @param {String} indexPattern The index pattern name * @param {String} ccs The optional cluster-prefix to prepend. * @return {String} The index pattern with the {@code cluster} prefix appropriately prepended. */ -export function prefixIndexPattern(config, indexPatternName, ccs) { +export function prefixIndexPattern(config, indexPattern, ccs) { const ccsEnabled = config.get('xpack.monitoring.ccs.enabled'); - const indexPattern = config.get(indexPatternName); if (!ccsEnabled || !ccs) { return indexPattern; diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/verify_monitoring_auth.js b/x-pack/plugins/monitoring/server/lib/elasticsearch/verify_monitoring_auth.js index 609fc9d46394a..5462cd492fbc8 100644 --- a/x-pack/plugins/monitoring/server/lib/elasticsearch/verify_monitoring_auth.js +++ b/x-pack/plugins/monitoring/server/lib/elasticsearch/verify_monitoring_auth.js @@ -6,6 +6,7 @@ import { get } from 'lodash'; import Boom from 'boom'; +import { INDEX_PATTERN } from '../../../common/constants'; /* * Check the currently logged-in user's privileges for "read" privileges on the @@ -36,7 +37,6 @@ export async function verifyMonitoringAuth(req) { */ async function verifyHasPrivileges(req) { const { callWithRequest } = req.server.plugins.elasticsearch.getCluster('monitoring'); - const config = req.server.config(); const response = await callWithRequest(req, 'transport.request', { method: 'POST', @@ -44,7 +44,7 @@ async function verifyHasPrivileges(req) { body: { index: [ { - names: [ config.get('xpack.monitoring.index_pattern') ], // uses wildcard + names: [ INDEX_PATTERN ], // uses wildcard privileges: [ 'read' ] } ] diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/alerts/index.js b/x-pack/plugins/monitoring/server/routes/api/v1/alerts/index.js index d9cea7f88b322..093e71e66ee2d 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/alerts/index.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/alerts/index.js @@ -9,6 +9,7 @@ import { alertsClusterSearch } from '../../../../cluster_alerts/alerts_cluster_s import { checkLicense } from '../../../../cluster_alerts/check_license'; import { getClusterLicense } from '../../../../lib/cluster/get_cluster_license'; import { prefixIndexPattern } from '../../../../lib/ccs_utils'; +import { INDEX_PATTERN_ELASTICSEARCH, INDEX_ALERTS } from '../../../../../common/constants'; /* * Cluster Alerts route. @@ -35,8 +36,8 @@ export function clusterAlertsRoute(server) { const config = server.config(); const ccs = req.payload.ccs; const clusterUuid = req.params.clusterUuid; - const esIndexPattern = prefixIndexPattern(config, 'xpack.monitoring.elasticsearch.index_pattern', ccs); - const alertsIndex = prefixIndexPattern(config, 'xpack.monitoring.cluster_alerts.index', ccs); + const esIndexPattern = prefixIndexPattern(config, INDEX_PATTERN_ELASTICSEARCH, ccs); + const alertsIndex = prefixIndexPattern(config, INDEX_ALERTS, ccs); const options = { start: req.payload.timeRange.min, end: req.payload.timeRange.max diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/apm/instance.js b/x-pack/plugins/monitoring/server/routes/api/v1/apm/instance.js index 7f9a79046d994..fad9e59902eed 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/apm/instance.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/apm/instance.js @@ -10,6 +10,7 @@ import { getMetrics } from '../../../../lib/details/get_metrics'; import { metricSet } from './metric_set_overview'; import { handleError } from '../../../../lib/errors'; import { getApmInfo } from '../../../../lib/apm'; +import { INDEX_PATTERN_BEATS } from '../../../../../common/constants'; export function apmInstanceRoute(server) { server.route({ @@ -35,7 +36,7 @@ export function apmInstanceRoute(server) { const config = server.config(); const clusterUuid = req.params.clusterUuid; const ccs = req.payload.ccs; - const apmIndexPattern = prefixIndexPattern(config, 'xpack.monitoring.beats.index_pattern', ccs); + const apmIndexPattern = prefixIndexPattern(config, INDEX_PATTERN_BEATS, ccs); try { const [ metrics, apmSummary ] = await Promise.all([ diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/apm/instances.js b/x-pack/plugins/monitoring/server/routes/api/v1/apm/instances.js index ec8c3cdd8d861..6a5ab2c45b9dc 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/apm/instances.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/apm/instances.js @@ -8,6 +8,7 @@ import Joi from 'joi'; import { prefixIndexPattern } from '../../../../lib/ccs_utils'; import { getStats, getApms } from '../../../../lib/apm'; import { handleError } from '../../../../lib/errors'; +import { INDEX_PATTERN_BEATS } from '../../../../../common/constants'; export function apmInstancesRoute(server) { server.route({ @@ -31,7 +32,7 @@ export function apmInstancesRoute(server) { const config = server.config(); const ccs = req.payload.ccs; const clusterUuid = req.params.clusterUuid; - const apmIndexPattern = prefixIndexPattern(config, 'xpack.monitoring.beats.index_pattern', ccs); + const apmIndexPattern = prefixIndexPattern(config, INDEX_PATTERN_BEATS, ccs); try { diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/apm/overview.js b/x-pack/plugins/monitoring/server/routes/api/v1/apm/overview.js index ecad1a444f7b0..66e39ce61ff77 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/apm/overview.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/apm/overview.js @@ -10,6 +10,7 @@ import { getMetrics } from '../../../../lib/details/get_metrics'; import { metricSet } from './metric_set_overview'; import { handleError } from '../../../../lib/errors'; import { getApmClusterStatus } from './_get_apm_cluster_status'; +import { INDEX_PATTERN_BEATS } from '../../../../../common/constants'; export function apmOverviewRoute(server) { server.route({ @@ -33,7 +34,7 @@ export function apmOverviewRoute(server) { const config = server.config(); const ccs = req.payload.ccs; const clusterUuid = req.params.clusterUuid; - const apmIndexPattern = prefixIndexPattern(config, 'xpack.monitoring.beats.index_pattern', ccs); + const apmIndexPattern = prefixIndexPattern(config, INDEX_PATTERN_BEATS, ccs); try { const [ diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/beats/beat_detail.js b/x-pack/plugins/monitoring/server/routes/api/v1/beats/beat_detail.js index 7bce52319c7e9..b8020fd007ba9 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/beats/beat_detail.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/beats/beat_detail.js @@ -10,6 +10,7 @@ import { getBeatSummary } from '../../../../lib/beats'; import { getMetrics } from '../../../../lib/details/get_metrics'; import { handleError } from '../../../../lib/errors'; import { metricSet } from './metric_set_detail'; +import { INDEX_PATTERN_BEATS } from '../../../../../common/constants'; export function beatsDetailRoute(server) { server.route({ @@ -36,7 +37,7 @@ export function beatsDetailRoute(server) { const beatUuid = req.params.beatUuid; const config = server.config(); const ccs = req.payload.ccs; - const beatsIndexPattern = prefixIndexPattern(config, 'xpack.monitoring.beats.index_pattern', ccs); + const beatsIndexPattern = prefixIndexPattern(config, INDEX_PATTERN_BEATS, ccs); const summaryOptions = { clusterUuid, diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/beats/beats.js b/x-pack/plugins/monitoring/server/routes/api/v1/beats/beats.js index f2e7a46f0278a..282bbf7a353a4 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/beats/beats.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/beats/beats.js @@ -8,6 +8,7 @@ import Joi from 'joi'; import { prefixIndexPattern } from '../../../../lib/ccs_utils'; import { getStats, getBeats } from '../../../../lib/beats'; import { handleError } from '../../../../lib/errors'; +import { INDEX_PATTERN_BEATS } from '../../../../../common/constants'; export function beatsListingRoute(server) { server.route({ @@ -32,7 +33,7 @@ export function beatsListingRoute(server) { const config = server.config(); const ccs = req.payload.ccs; const clusterUuid = req.params.clusterUuid; - const beatsIndexPattern = prefixIndexPattern(config, 'xpack.monitoring.beats.index_pattern', ccs); + const beatsIndexPattern = prefixIndexPattern(config, INDEX_PATTERN_BEATS, ccs); try { diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/beats/overview.js b/x-pack/plugins/monitoring/server/routes/api/v1/beats/overview.js index b2f0267265538..0fc32f5c3de58 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/beats/overview.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/beats/overview.js @@ -10,6 +10,7 @@ import { getMetrics } from '../../../../lib/details/get_metrics'; import { getLatestStats, getStats } from '../../../../lib/beats'; import { handleError } from '../../../../lib/errors'; import { metricSet } from './metric_set_overview'; +import { INDEX_PATTERN_BEATS } from '../../../../../common/constants'; export function beatsOverviewRoute(server) { server.route({ @@ -34,7 +35,7 @@ export function beatsOverviewRoute(server) { const config = server.config(); const ccs = req.payload.ccs; const clusterUuid = req.params.clusterUuid; - const beatsIndexPattern = prefixIndexPattern(config, 'xpack.monitoring.beats.index_pattern', ccs); + const beatsIndexPattern = prefixIndexPattern(config, INDEX_PATTERN_BEATS, ccs); try { const [ diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/cluster/cluster.js b/x-pack/plugins/monitoring/server/routes/api/v1/cluster/cluster.js index 1b0d3d83f1316..7c3344626f852 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/cluster/cluster.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/cluster/cluster.js @@ -8,6 +8,13 @@ import Joi from 'joi'; import { getClustersFromRequest } from '../../../../lib/cluster/get_clusters_from_request'; import { handleError } from '../../../../lib/errors'; import { prefixIndexPattern } from '../../../../lib/ccs_utils'; +import { + INDEX_PATTERN_KIBANA, + INDEX_PATTERN_ELASTICSEARCH, + INDEX_PATTERN_LOGSTASH, + INDEX_PATTERN_BEATS, + INDEX_ALERTS +} from '../../../../../common/constants'; export function clusterRoute(server) { /* @@ -33,12 +40,12 @@ export function clusterRoute(server) { handler: (req) => { const config = server.config(); const ccs = req.payload.ccs; - const esIndexPattern = prefixIndexPattern(config, 'xpack.monitoring.elasticsearch.index_pattern', ccs); - const kbnIndexPattern = prefixIndexPattern(config, 'xpack.monitoring.kibana.index_pattern', ccs); - const lsIndexPattern = prefixIndexPattern(config, 'xpack.monitoring.logstash.index_pattern', ccs); - const beatsIndexPattern = prefixIndexPattern(config, 'xpack.monitoring.beats.index_pattern', ccs); - const apmIndexPattern = prefixIndexPattern(config, 'xpack.monitoring.beats.index_pattern', ccs); - const alertsIndex = prefixIndexPattern(config, 'xpack.monitoring.cluster_alerts.index', ccs); + const esIndexPattern = prefixIndexPattern(config, INDEX_PATTERN_ELASTICSEARCH, ccs); + const kbnIndexPattern = prefixIndexPattern(config, INDEX_PATTERN_KIBANA, ccs); + const lsIndexPattern = prefixIndexPattern(config, INDEX_PATTERN_LOGSTASH, ccs); + const beatsIndexPattern = prefixIndexPattern(config, INDEX_PATTERN_BEATS, ccs); + const apmIndexPattern = prefixIndexPattern(config, INDEX_PATTERN_BEATS, ccs); + const alertsIndex = prefixIndexPattern(config, INDEX_ALERTS, ccs); const indexPatterns = { esIndexPattern, kbnIndexPattern, lsIndexPattern, beatsIndexPattern, apmIndexPattern, alertsIndex }; const options = { clusterUuid: req.params.clusterUuid, diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/cluster/clusters.js b/x-pack/plugins/monitoring/server/routes/api/v1/cluster/clusters.js index 5d3a903ab38d7..2ce91020623df 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/cluster/clusters.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/cluster/clusters.js @@ -9,6 +9,13 @@ import { getClustersFromRequest } from '../../../../lib/cluster/get_clusters_fro import { verifyMonitoringAuth } from '../../../../lib/elasticsearch/verify_monitoring_auth'; import { handleError } from '../../../../lib/errors'; import { prefixIndexPattern } from '../../../../lib/ccs_utils'; +import { + INDEX_PATTERN_ELASTICSEARCH, + INDEX_PATTERN_KIBANA, + INDEX_PATTERN_LOGSTASH, + INDEX_PATTERN_BEATS, + INDEX_ALERTS +} from '../../../../../common/constants'; export function clustersRoute(server) { /* @@ -40,12 +47,12 @@ export function clustersRoute(server) { // wildcard means to search _all_ clusters const ccs = '*'; const config = server.config(); - const esIndexPattern = prefixIndexPattern(config, 'xpack.monitoring.elasticsearch.index_pattern', ccs); - const kbnIndexPattern = prefixIndexPattern(config, 'xpack.monitoring.kibana.index_pattern', ccs); - const lsIndexPattern = prefixIndexPattern(config, 'xpack.monitoring.logstash.index_pattern', ccs); - const beatsIndexPattern = prefixIndexPattern(config, 'xpack.monitoring.beats.index_pattern', ccs); - const apmIndexPattern = prefixIndexPattern(config, 'xpack.monitoring.beats.index_pattern', ccs); - const alertsIndex = prefixIndexPattern(config, 'xpack.monitoring.cluster_alerts.index', ccs); + const esIndexPattern = prefixIndexPattern(config, INDEX_PATTERN_ELASTICSEARCH, ccs); + const kbnIndexPattern = prefixIndexPattern(config, INDEX_PATTERN_KIBANA, ccs); + const lsIndexPattern = prefixIndexPattern(config, INDEX_PATTERN_LOGSTASH, ccs); + const beatsIndexPattern = prefixIndexPattern(config, INDEX_PATTERN_BEATS, ccs); + const apmIndexPattern = prefixIndexPattern(config, INDEX_PATTERN_BEATS, ccs); + const alertsIndex = prefixIndexPattern(config, INDEX_ALERTS, ccs); const indexPatterns = { esIndexPattern, kbnIndexPattern, lsIndexPattern, beatsIndexPattern, apmIndexPattern, alertsIndex }; clusters = await getClustersFromRequest(req, indexPatterns); diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ccr.js b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ccr.js index b3e85cdcee1e8..9047d1f676e85 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ccr.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ccr.js @@ -9,6 +9,7 @@ import moment from 'moment'; import { get, groupBy } from 'lodash'; import { handleError } from '../../../../lib/errors/handle_error'; import { prefixIndexPattern } from '../../../../lib/ccs_utils'; +import { INDEX_PATTERN_ELASTICSEARCH } from '../../../../../common/constants'; function getBucketScript(max, min) { return { @@ -185,7 +186,7 @@ export function ccrRoute(server) { async handler(req) { const config = server.config(); const ccs = req.payload.ccs; - const esIndexPattern = prefixIndexPattern(config, 'xpack.monitoring.elasticsearch.index_pattern', ccs); + const esIndexPattern = prefixIndexPattern(config, INDEX_PATTERN_ELASTICSEARCH, ccs); try { const { callWithRequest } = req.server.plugins.elasticsearch.getCluster('monitoring'); diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ccr_shard.js b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ccr_shard.js index cc8268a359911..955c32d5b6a79 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ccr_shard.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ccr_shard.js @@ -10,6 +10,7 @@ import Joi from 'joi'; import { handleError } from '../../../../lib/errors/handle_error'; import { prefixIndexPattern } from '../../../../lib/ccs_utils'; import { getMetrics } from '../../../../lib/details/get_metrics'; +import { INDEX_PATTERN_ELASTICSEARCH } from '../../../../../common/constants'; function getFormattedLeaderIndex(leaderIndex) { let leader = leaderIndex; @@ -92,7 +93,7 @@ export function ccrShardRoute(server) { const index = req.params.index; const shardId = req.params.shardId; const ccs = req.payload.ccs; - const esIndexPattern = prefixIndexPattern(config, 'xpack.monitoring.elasticsearch.index_pattern', ccs); + const esIndexPattern = prefixIndexPattern(config, INDEX_PATTERN_ELASTICSEARCH, ccs); const filters = [ { diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/index_detail.js b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/index_detail.js index e27ec33db86ab..76ba483b0fdd7 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/index_detail.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/index_detail.js @@ -13,6 +13,7 @@ import { getShardAllocation, getShardStats } from '../../../../lib/elasticsearch import { handleError } from '../../../../lib/errors/handle_error'; import { prefixIndexPattern } from '../../../../lib/ccs_utils'; import { metricSet } from './metric_set_index_detail'; +import { INDEX_PATTERN_ELASTICSEARCH } from '../../../../../common/constants'; const { advanced: metricSetAdvanced, overview: metricSetOverview } = metricSet; @@ -45,7 +46,7 @@ export function esIndexRoute(server) { const indexUuid = req.params.id; const start = req.payload.timeRange.min; const end = req.payload.timeRange.max; - const esIndexPattern = prefixIndexPattern(config, 'xpack.monitoring.elasticsearch.index_pattern', ccs); + const esIndexPattern = prefixIndexPattern(config, INDEX_PATTERN_ELASTICSEARCH, ccs); const isAdvanced = req.payload.is_advanced; const metricSet = isAdvanced ? metricSetAdvanced : metricSetOverview; diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/indices.js b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/indices.js index 4e9d434372bae..22ad4f6e330c4 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/indices.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/indices.js @@ -11,6 +11,7 @@ import { getIndices } from '../../../../lib/elasticsearch/indices'; import { getShardStats } from '../../../../lib/elasticsearch/shards'; import { handleError } from '../../../../lib/errors/handle_error'; import { prefixIndexPattern } from '../../../../lib/ccs_utils'; +import { INDEX_PATTERN_ELASTICSEARCH } from '../../../../../common/constants'; export function esIndicesRoute(server) { server.route({ @@ -38,7 +39,7 @@ export function esIndicesRoute(server) { const { clusterUuid } = req.params; const { show_system_indices: showSystemIndices } = req.query; const { ccs } = req.payload; - const esIndexPattern = prefixIndexPattern(config, 'xpack.monitoring.elasticsearch.index_pattern', ccs); + const esIndexPattern = prefixIndexPattern(config, INDEX_PATTERN_ELASTICSEARCH, ccs); try { const clusterStats = await getClusterStats(req, esIndexPattern, clusterUuid); diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ml_jobs.js b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ml_jobs.js index f6387bd1fa25e..a84e63539554e 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ml_jobs.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ml_jobs.js @@ -11,6 +11,7 @@ import { getMlJobs } from '../../../../lib/elasticsearch/get_ml_jobs'; import { getShardStats } from '../../../../lib/elasticsearch/shards'; import { handleError } from '../../../../lib/errors/handle_error'; import { prefixIndexPattern } from '../../../../lib/ccs_utils'; +import { INDEX_PATTERN_ELASTICSEARCH } from '../../../../../common/constants'; export function mlJobRoute(server) { server.route({ @@ -34,7 +35,7 @@ export function mlJobRoute(server) { const config = server.config(); const ccs = req.payload.ccs; const clusterUuid = req.params.clusterUuid; - const esIndexPattern = prefixIndexPattern(config, 'xpack.monitoring.elasticsearch.index_pattern', ccs); + const esIndexPattern = prefixIndexPattern(config, INDEX_PATTERN_ELASTICSEARCH, ccs); try { const clusterStats = await getClusterStats(req, esIndexPattern, clusterUuid); diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/node_detail.js b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/node_detail.js index 6f5d37930e7c1..2bec1646f896e 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/node_detail.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/node_detail.js @@ -13,6 +13,7 @@ import { getMetrics } from '../../../../lib/details/get_metrics'; import { handleError } from '../../../../lib/errors/handle_error'; import { prefixIndexPattern } from '../../../../lib/ccs_utils'; import { metricSets } from './metric_set_node_detail'; +import { INDEX_PATTERN_ELASTICSEARCH } from '../../../../../common/constants'; const { advanced: metricSetAdvanced, overview: metricSetOverview } = metricSets; @@ -45,7 +46,7 @@ export function esNodeRoute(server) { const nodeUuid = req.params.nodeUuid; const start = req.payload.timeRange.min; const end = req.payload.timeRange.max; - const esIndexPattern = prefixIndexPattern(config, 'xpack.monitoring.elasticsearch.index_pattern', ccs); + const esIndexPattern = prefixIndexPattern(config, INDEX_PATTERN_ELASTICSEARCH, ccs); const isAdvanced = req.payload.is_advanced; let metricSet; diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/nodes.js b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/nodes.js index 7fe93e1394b21..217cc3d2262c9 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/nodes.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/nodes.js @@ -11,6 +11,7 @@ import { getNodes } from '../../../../lib/elasticsearch/nodes'; import { getShardStats } from '../../../../lib/elasticsearch/shards'; import { handleError } from '../../../../lib/errors/handle_error'; import { prefixIndexPattern } from '../../../../lib/ccs_utils'; +import { INDEX_PATTERN_ELASTICSEARCH } from '../../../../../common/constants'; export function esNodesRoute(server) { server.route({ @@ -34,7 +35,7 @@ export function esNodesRoute(server) { const config = server.config(); const ccs = req.payload.ccs; const clusterUuid = req.params.clusterUuid; - const esIndexPattern = prefixIndexPattern(config, 'xpack.monitoring.elasticsearch.index_pattern', ccs); + const esIndexPattern = prefixIndexPattern(config, INDEX_PATTERN_ELASTICSEARCH, ccs); try { const clusterStats = await getClusterStats(req, esIndexPattern, clusterUuid); diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/overview.js b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/overview.js index ad1d65c67b6b0..068c36f5064e9 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/overview.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/overview.js @@ -13,6 +13,7 @@ import { getShardStats } from '../../../../lib/elasticsearch/shards'; import { handleError } from '../../../../lib/errors/handle_error'; import { prefixIndexPattern } from '../../../../lib/ccs_utils'; import { metricSet } from './metric_set_overview'; +import { INDEX_PATTERN_ELASTICSEARCH } from '../../../../../common/constants'; export function esOverviewRoute(server) { server.route({ @@ -36,7 +37,7 @@ export function esOverviewRoute(server) { const config = server.config(); const ccs = req.payload.ccs; const clusterUuid = req.params.clusterUuid; - const esIndexPattern = prefixIndexPattern(config, 'xpack.monitoring.elasticsearch.index_pattern', ccs); + const esIndexPattern = prefixIndexPattern(config, INDEX_PATTERN_ELASTICSEARCH, ccs); try { const [ clusterStats, metrics, shardActivity ] = await Promise.all([ diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/kibana/instance.js b/x-pack/plugins/monitoring/server/routes/api/v1/kibana/instance.js index 6486ea530d53f..39d16c4e6fa97 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/kibana/instance.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/kibana/instance.js @@ -10,6 +10,7 @@ import { handleError } from '../../../../lib/errors'; import { getMetrics } from '../../../../lib/details/get_metrics'; import { prefixIndexPattern } from '../../../../lib/ccs_utils'; import { metricSet } from './metric_set_instance'; +import { INDEX_PATTERN_KIBANA } from '../../../../../common/constants'; /** * Kibana instance: This will fetch all data required to display a Kibana @@ -41,7 +42,7 @@ export function kibanaInstanceRoute(server) { const ccs = req.payload.ccs; const clusterUuid = req.params.clusterUuid; const kibanaUuid = req.params.kibanaUuid; - const kbnIndexPattern = prefixIndexPattern(config, 'xpack.monitoring.kibana.index_pattern', ccs); + const kbnIndexPattern = prefixIndexPattern(config, INDEX_PATTERN_KIBANA, ccs); try { const [ metrics, kibanaSummary ] = await Promise.all([ diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/kibana/instances.js b/x-pack/plugins/monitoring/server/routes/api/v1/kibana/instances.js index 580172ecd7ece..ca076fd8aec64 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/kibana/instances.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/kibana/instances.js @@ -9,6 +9,7 @@ import { prefixIndexPattern } from '../../../../lib/ccs_utils'; import { getKibanaClusterStatus } from './_get_kibana_cluster_status'; import { getKibanas } from '../../../../lib/kibana/get_kibanas'; import { handleError } from '../../../../lib/errors'; +import { INDEX_PATTERN_KIBANA } from '../../../../../common/constants'; export function kibanaInstancesRoute(server) { /** @@ -35,7 +36,7 @@ export function kibanaInstancesRoute(server) { const config = server.config(); const ccs = req.payload.ccs; const clusterUuid = req.params.clusterUuid; - const kbnIndexPattern = prefixIndexPattern(config, 'xpack.monitoring.kibana.index_pattern', ccs); + const kbnIndexPattern = prefixIndexPattern(config, INDEX_PATTERN_KIBANA, ccs); try { const [ clusterStatus, kibanas ] = await Promise.all([ diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/kibana/overview.js b/x-pack/plugins/monitoring/server/routes/api/v1/kibana/overview.js index b7f396eef138f..275ac81127b97 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/kibana/overview.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/kibana/overview.js @@ -10,6 +10,7 @@ import { getKibanaClusterStatus } from './_get_kibana_cluster_status'; import { getMetrics } from '../../../../lib/details/get_metrics'; import { metricSet } from './metric_set_overview'; import { handleError } from '../../../../lib/errors'; +import { INDEX_PATTERN_KIBANA } from '../../../../../common/constants'; export function kibanaOverviewRoute(server) { /** @@ -36,7 +37,7 @@ export function kibanaOverviewRoute(server) { const config = server.config(); const ccs = req.payload.ccs; const clusterUuid = req.params.clusterUuid; - const kbnIndexPattern = prefixIndexPattern(config, 'xpack.monitoring.kibana.index_pattern', ccs); + const kbnIndexPattern = prefixIndexPattern(config, INDEX_PATTERN_KIBANA, ccs); try { const [ clusterStatus, metrics ] = await Promise.all([ diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/logstash/node.js b/x-pack/plugins/monitoring/server/routes/api/v1/logstash/node.js index 6916fa871e810..28755903c82b8 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/logstash/node.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/logstash/node.js @@ -10,6 +10,7 @@ import { handleError } from '../../../../lib/errors'; import { getMetrics } from '../../../../lib/details/get_metrics'; import { prefixIndexPattern } from '../../../../lib/ccs_utils'; import { metricSets } from './metric_set_node'; +import { INDEX_PATTERN_LOGSTASH } from '../../../../../common/constants'; const { advanced: metricSetAdvanced, overview: metricSetOverview } = metricSets; @@ -50,7 +51,7 @@ export function logstashNodeRoute(server) { const config = server.config(); const ccs = req.payload.ccs; const clusterUuid = req.params.clusterUuid; - const lsIndexPattern = prefixIndexPattern(config, 'xpack.monitoring.logstash.index_pattern', ccs); + const lsIndexPattern = prefixIndexPattern(config, INDEX_PATTERN_LOGSTASH, ccs); const logstashUuid = req.params.logstashUuid; let metricSet; diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/logstash/nodes.js b/x-pack/plugins/monitoring/server/routes/api/v1/logstash/nodes.js index 34e3b6cb2a4cc..f0af65bccaff5 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/logstash/nodes.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/logstash/nodes.js @@ -9,6 +9,7 @@ import { getClusterStatus } from '../../../../lib/logstash/get_cluster_status'; import { getNodes } from '../../../../lib/logstash/get_nodes'; import { handleError } from '../../../../lib/errors'; import { prefixIndexPattern } from '../../../../lib/ccs_utils'; +import { INDEX_PATTERN_LOGSTASH } from '../../../../../common/constants'; /* * Logstash Nodes route. @@ -45,7 +46,7 @@ export function logstashNodesRoute(server) { const config = server.config(); const ccs = req.payload.ccs; const clusterUuid = req.params.clusterUuid; - const lsIndexPattern = prefixIndexPattern(config, 'xpack.monitoring.logstash.index_pattern', ccs); + const lsIndexPattern = prefixIndexPattern(config, INDEX_PATTERN_LOGSTASH, ccs); try { const [ clusterStatus, nodes ] = await Promise.all([ diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/logstash/overview.js b/x-pack/plugins/monitoring/server/routes/api/v1/logstash/overview.js index 35fe0271a1885..c8cfc5b74c9fc 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/logstash/overview.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/logstash/overview.js @@ -10,6 +10,7 @@ import { getMetrics } from '../../../../lib/details/get_metrics'; import { handleError } from '../../../../lib/errors'; import { prefixIndexPattern } from '../../../../lib/ccs_utils'; import { metricSet } from './metric_set_overview'; +import { INDEX_PATTERN_LOGSTASH } from '../../../../../common/constants'; /* * Logstash Overview route. @@ -46,7 +47,7 @@ export function logstashOverviewRoute(server) { const config = server.config(); const ccs = req.payload.ccs; const clusterUuid = req.params.clusterUuid; - const lsIndexPattern = prefixIndexPattern(config, 'xpack.monitoring.logstash.index_pattern', ccs); + const lsIndexPattern = prefixIndexPattern(config, INDEX_PATTERN_LOGSTASH, ccs); try { const [ metrics, clusterStatus ] = await Promise.all([ diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/logstash/pipeline.js b/x-pack/plugins/monitoring/server/routes/api/v1/logstash/pipeline.js index 3a93dd57a9818..433c48f02b3ea 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/logstash/pipeline.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/logstash/pipeline.js @@ -8,6 +8,7 @@ import Joi from 'joi'; import { handleError } from '../../../../lib/errors'; import { getPipeline } from '../../../../lib/logstash/get_pipeline'; import { prefixIndexPattern } from '../../../../lib/ccs_utils'; +import { INDEX_PATTERN_LOGSTASH } from '../../../../../common/constants'; /* * Logstash Pipeline route. @@ -41,7 +42,7 @@ export function logstashPipelineRoute(server) { const config = server.config(); const ccs = req.payload.ccs; const clusterUuid = req.params.clusterUuid; - const lsIndexPattern = prefixIndexPattern(config, 'xpack.monitoring.logstash.index_pattern', ccs); + const lsIndexPattern = prefixIndexPattern(config, INDEX_PATTERN_LOGSTASH, ccs); const pipelineId = req.params.pipelineId; // Optional params default to empty string, set to null to be more explicit. diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/logstash/pipelines/cluster_pipelines.js b/x-pack/plugins/monitoring/server/routes/api/v1/logstash/pipelines/cluster_pipelines.js index 5a90e4ec9e9dc..247e734360dc9 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/logstash/pipelines/cluster_pipelines.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/logstash/pipelines/cluster_pipelines.js @@ -9,6 +9,7 @@ import { getClusterStatus } from '../../../../../lib/logstash/get_cluster_status import { getPipelines, processPipelinesAPIResponse } from '../../../../../lib/logstash/get_pipelines'; import { handleError } from '../../../../../lib/errors'; import { prefixIndexPattern } from '../../../../../lib/ccs_utils'; +import { INDEX_PATTERN_LOGSTASH } from '../../../../../../common/constants'; /** * Retrieve pipelines for a cluster @@ -35,7 +36,7 @@ export function logstashClusterPipelinesRoute(server) { const config = server.config(); const { ccs } = req.payload; const clusterUuid = req.params.clusterUuid; - const lsIndexPattern = prefixIndexPattern(config, 'xpack.monitoring.logstash.index_pattern', ccs); + const lsIndexPattern = prefixIndexPattern(config, INDEX_PATTERN_LOGSTASH, ccs); const throughputMetric = 'logstash_cluster_pipeline_throughput'; const nodesCountMetric = 'logstash_cluster_pipeline_nodes_count'; diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/logstash/pipelines/node_pipelines.js b/x-pack/plugins/monitoring/server/routes/api/v1/logstash/pipelines/node_pipelines.js index 8aace30bb61f3..faec0791d7c32 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/logstash/pipelines/node_pipelines.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/logstash/pipelines/node_pipelines.js @@ -9,6 +9,7 @@ import { getNodeInfo } from '../../../../../lib/logstash/get_node_info'; import { getPipelines, processPipelinesAPIResponse } from '../../../../../lib/logstash/get_pipelines'; import { handleError } from '../../../../../lib/errors'; import { prefixIndexPattern } from '../../../../../lib/ccs_utils'; +import { INDEX_PATTERN_LOGSTASH } from '../../../../../../common/constants'; /** * Retrieve pipelines for a node @@ -36,7 +37,7 @@ export function logstashNodePipelinesRoute(server) { const config = server.config(); const { ccs } = req.payload; const { clusterUuid, logstashUuid } = req.params; - const lsIndexPattern = prefixIndexPattern(config, 'xpack.monitoring.logstash.index_pattern', ccs); + const lsIndexPattern = prefixIndexPattern(config, INDEX_PATTERN_LOGSTASH, ccs); const throughputMetric = 'logstash_node_pipeline_throughput'; const nodesCountMetric = 'logstash_node_pipeline_nodes_count'; const metricSet = [ diff --git a/x-pack/plugins/rollup/public/extend_index_management/index.js b/x-pack/plugins/rollup/public/extend_index_management/index.js index aa376736786e2..b0d94b94029d2 100644 --- a/x-pack/plugins/rollup/public/extend_index_management/index.js +++ b/x-pack/plugins/rollup/public/extend_index_management/index.js @@ -27,7 +27,8 @@ export const rollupBadgeExtension = { label: i18n.translate('xpack.rollupJobs.indexMgmtBadge.rollupLabel', { defaultMessage: 'Rollup', }), - color: 'secondary' + color: 'secondary', + filterExpression: 'isRollupIndex:true' }; addBadgeExtension(rollupBadgeExtension); diff --git a/x-pack/plugins/rollup/server/usage/collector.js b/x-pack/plugins/rollup/server/usage/collector.js index 4cb9fba66ca67..d017740def9e3 100644 --- a/x-pack/plugins/rollup/server/usage/collector.js +++ b/x-pack/plugins/rollup/server/usage/collector.js @@ -102,8 +102,9 @@ async function fetchRollupVisualizations(kibanaIndex, callCluster, rollupIndexPa index: kibanaIndex, ignoreUnavailable: true, filterPath: [ - 'hits.hits._source.visualization.savedSearchId', + 'hits.hits._source.visualization.savedSearchRefName', 'hits.hits._source.visualization.kibanaSavedObjectMeta', + 'hits.hits._source.references', ], body: { query: { @@ -128,19 +129,21 @@ async function fetchRollupVisualizations(kibanaIndex, callCluster, rollupIndexPa const { _source: { visualization: { - savedSearchId, + savedSearchRefName, kibanaSavedObjectMeta: { searchSourceJSON, }, }, + references = [], }, } = visualization; const searchSource = JSON.parse(searchSourceJSON); - if (savedSearchId) { + if (savedSearchRefName) { // This visualization depends upon a saved search. - if (rollupSavedSearchesToFlagMap[savedSearchId]) { + const savedSearch = references.find(ref => ref.name === savedSearchRefName); + if (rollupSavedSearchesToFlagMap[savedSearch.id]) { rollupVisualizations++; rollupVisualizationsFromSavedSearches++; } diff --git a/x-pack/plugins/spaces/server/lib/saved_objects_client/spaces_saved_objects_client.ts b/x-pack/plugins/spaces/server/lib/saved_objects_client/spaces_saved_objects_client.ts index 5e86cd4f08c5e..9a3203305b59d 100644 --- a/x-pack/plugins/spaces/server/lib/saved_objects_client/spaces_saved_objects_client.ts +++ b/x-pack/plugins/spaces/server/lib/saved_objects_client/spaces_saved_objects_client.ts @@ -149,6 +149,7 @@ export class SpacesSavedObjectsClient implements SavedObjectsClient { * @property {string} [options.sortOrder] * @property {Array} [options.fields] * @property {string} [options.namespace] + * @property {object} [options.hasReference] - { type, id } * @returns {promise} - { saved_objects: [{ id, type, version, attributes }], total, per_page, page } */ public async find(options: FindOptions = {}) { diff --git a/x-pack/plugins/spaces/server/lib/space_request_interceptors.test.ts b/x-pack/plugins/spaces/server/lib/space_request_interceptors.test.ts index 9d9ad1417a3e9..7604cbc06f712 100644 --- a/x-pack/plugins/spaces/server/lib/space_request_interceptors.test.ts +++ b/x-pack/plugins/spaces/server/lib/space_request_interceptors.test.ts @@ -229,6 +229,7 @@ describe('interceptors', () => { attributes: { name: 'a space', }, + references: [], }, ]; @@ -265,6 +266,7 @@ describe('interceptors', () => { attributes: { name: 'a space', }, + references: [], }, ]; @@ -302,6 +304,7 @@ describe('interceptors', () => { attributes: { name: 'a space', }, + references: [], }, ]; @@ -348,6 +351,7 @@ describe('interceptors', () => { attributes: { name: 'Default Space', }, + references: [], }, ]; @@ -379,6 +383,7 @@ describe('interceptors', () => { attributes: { name: 'a space', }, + references: [], }, { id: 'b-space', @@ -386,6 +391,7 @@ describe('interceptors', () => { attributes: { name: 'b space', }, + references: [], }, ]; diff --git a/x-pack/plugins/uptime/index.ts b/x-pack/plugins/uptime/index.ts index f369c3d73681d..ac9322e7bd141 100644 --- a/x-pack/plugins/uptime/index.ts +++ b/x-pack/plugins/uptime/index.ts @@ -23,7 +23,7 @@ export const uptime = (kibana: any) => description: 'The description text that will be shown to users in Kibana', }), icon: 'plugins/uptime/icons/heartbeat_white.svg', - euiIconType: 'heartbeatApp', + euiIconType: 'uptimeApp', title: 'Uptime', main: 'plugins/uptime/app', order: 8900, diff --git a/x-pack/plugins/xpack_main/common/constants.js b/x-pack/plugins/xpack_main/common/constants.js index 5f0b956fb1da6..1dec1a2264ea0 100644 --- a/x-pack/plugins/xpack_main/common/constants.js +++ b/x-pack/plugins/xpack_main/common/constants.js @@ -34,6 +34,12 @@ export const KIBANA_SYSTEM_ID = 'kibana'; */ export const BEATS_SYSTEM_ID = 'beats'; +/** + * The name of the Apm System ID used to publish and look up Apm stats through the Monitoring system. + * @type {string} + */ +export const APM_SYSTEM_ID = 'beats'; + /** * The name of the Kibana System ID used to look up Logstash stats through the Monitoring system. * @type {string} diff --git a/x-pack/plugins/xpack_main/server/lib/telemetry/monitoring/get_beats_stats.js b/x-pack/plugins/xpack_main/server/lib/telemetry/monitoring/get_beats_stats.js index 321e2ebdad642..d38d8f519c047 100644 --- a/x-pack/plugins/xpack_main/server/lib/telemetry/monitoring/get_beats_stats.js +++ b/x-pack/plugins/xpack_main/server/lib/telemetry/monitoring/get_beats_stats.js @@ -6,6 +6,7 @@ import { get } from 'lodash'; import { createQuery } from './create_query'; +import { INDEX_PATTERN_BEATS } from '../../../../../monitoring/common/constants'; const HITS_SIZE = 10000; // maximum hits to receive from ES with each search @@ -173,10 +174,8 @@ export function processResults(results = [], { clusters, clusterHostSets, cluste * @return {Promise} */ async function fetchBeatsByType(server, callCluster, clusterUuids, start, end, { page = 0, ...options } = {}, type) { - const config = server.config(); - const params = { - index: config.get('xpack.monitoring.beats.index_pattern'), + index: INDEX_PATTERN_BEATS, ignoreUnavailable: true, filterPath: [ 'hits.hits._source.cluster_uuid', diff --git a/x-pack/plugins/xpack_main/server/lib/telemetry/monitoring/get_cluster_uuids.js b/x-pack/plugins/xpack_main/server/lib/telemetry/monitoring/get_cluster_uuids.js index f573a71b77d5e..9f8aac44a4728 100644 --- a/x-pack/plugins/xpack_main/server/lib/telemetry/monitoring/get_cluster_uuids.js +++ b/x-pack/plugins/xpack_main/server/lib/telemetry/monitoring/get_cluster_uuids.js @@ -6,6 +6,7 @@ import { get } from 'lodash'; import { createQuery } from './create_query'; +import { INDEX_PATTERN_ELASTICSEARCH } from '../../../../../monitoring/common/constants'; /** * Get a list of Cluster UUIDs that exist within the specified timespan. @@ -33,7 +34,7 @@ export function getClusterUuids(server, callCluster, start, end) { export function fetchClusterUuids(server, callCluster, start, end) { const config = server.config(); const params = { - index: config.get('xpack.monitoring.elasticsearch.index_pattern'), + index: INDEX_PATTERN_ELASTICSEARCH, size: 0, ignoreUnavailable: true, filterPath: 'aggregations.cluster_uuids.buckets.key', diff --git a/x-pack/plugins/xpack_main/server/lib/telemetry/monitoring/get_es_stats.js b/x-pack/plugins/xpack_main/server/lib/telemetry/monitoring/get_es_stats.js index 377ce8160f2b4..55198a5ec85d4 100644 --- a/x-pack/plugins/xpack_main/server/lib/telemetry/monitoring/get_es_stats.js +++ b/x-pack/plugins/xpack_main/server/lib/telemetry/monitoring/get_es_stats.js @@ -5,6 +5,7 @@ */ import { get } from 'lodash'; +import { INDEX_PATTERN_ELASTICSEARCH } from '../../../../../monitoring/common/constants'; /** * Get statistics for all selected Elasticsearch clusters. @@ -30,7 +31,7 @@ export function getElasticsearchStats(server, callCluster, clusterUuids) { export function fetchElasticsearchStats(server, callCluster, clusterUuids) { const config = server.config(); const params = { - index: config.get('xpack.monitoring.elasticsearch.index_pattern'), + index: INDEX_PATTERN_ELASTICSEARCH, size: config.get('xpack.monitoring.max_bucket_size'), ignoreUnavailable: true, filterPath: [ diff --git a/x-pack/plugins/xpack_main/server/lib/telemetry/monitoring/get_high_level_stats.js b/x-pack/plugins/xpack_main/server/lib/telemetry/monitoring/get_high_level_stats.js index b845fb5115492..fbbb81d6fd89e 100644 --- a/x-pack/plugins/xpack_main/server/lib/telemetry/monitoring/get_high_level_stats.js +++ b/x-pack/plugins/xpack_main/server/lib/telemetry/monitoring/get_high_level_stats.js @@ -6,6 +6,8 @@ import { get } from 'lodash'; import { createQuery } from './create_query'; +import { INDEX_PATTERN_KIBANA, INDEX_PATTERN_BEATS, INDEX_PATTERN_LOGSTASH } from '../../../../../monitoring/common/constants'; +import { KIBANA_SYSTEM_ID, BEATS_SYSTEM_ID, APM_SYSTEM_ID, LOGSTASH_SYSTEM_ID } from '../../../../common/constants'; /** * Update a counter associated with the {@code key}. @@ -139,6 +141,24 @@ function mapToList(map, keyName) { return list; } +/** + * Returns the right index pattern to find monitoring documents based on the product id + * + * @param {*} product The product id, which should be in the constants file + */ +function getIndexPatternForStackProduct(product) { + switch (product) { + case KIBANA_SYSTEM_ID: + return INDEX_PATTERN_KIBANA; + case BEATS_SYSTEM_ID: + case APM_SYSTEM_ID: + return INDEX_PATTERN_BEATS; + case LOGSTASH_SYSTEM_ID: + return INDEX_PATTERN_LOGSTASH; + } + return null; +} + /** * Get statistics about selected Elasticsearch clusters, for the selected {@code product}. * @@ -170,7 +190,7 @@ export function getHighLevelStats(server, callCluster, clusterUuids, start, end, export function fetchHighLevelStats(server, callCluster, clusterUuids, start, end, product) { const config = server.config(); const params = { - index: config.get(`xpack.monitoring.${product}.index_pattern`), + index: getIndexPatternForStackProduct(product), size: config.get('xpack.monitoring.max_bucket_size'), ignoreUnavailable: true, filterPath: [ diff --git a/x-pack/test/api_integration/apis/infra/index.js b/x-pack/test/api_integration/apis/infra/index.js index 595f931a1e8fc..1471e4fe8084a 100644 --- a/x-pack/test/api_integration/apis/infra/index.js +++ b/x-pack/test/api_integration/apis/infra/index.js @@ -14,5 +14,6 @@ export default function ({ loadTestFile }) { loadTestFile(require.resolve('./metrics')); loadTestFile(require.resolve('./sources')); loadTestFile(require.resolve('./waffle')); + loadTestFile(require.resolve('./log_item')); }); } diff --git a/x-pack/test/api_integration/apis/infra/log_item.ts b/x-pack/test/api_integration/apis/infra/log_item.ts new file mode 100644 index 0000000000000..bfdde32fa57ae --- /dev/null +++ b/x-pack/test/api_integration/apis/infra/log_item.ts @@ -0,0 +1,175 @@ +/* + * 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 'expect.js'; +import { flyoutItemQuery } from '../../../../plugins/infra/public/containers/logs/flyout_item.gql_query'; +import { FlyoutItemQuery } from '../../../../plugins/infra/public/graphql/types'; +import { KbnTestProvider } from './types'; + +const logItemTests: KbnTestProvider = ({ getService }) => { + const esArchiver = getService('esArchiver'); + const client = getService('infraOpsGraphQLClient'); + describe('Log Item GraphQL Endpoint', () => { + before(() => esArchiver.load('infra/metrics_and_logs')); + after(() => esArchiver.unload('infra/metrics_and_logs')); + + it('should basically work', () => { + return client + .query({ + query: flyoutItemQuery, + variables: { + sourceId: 'default', + itemId: 'yT2Mg2YBh-opCxJv8Vqj', + }, + }) + .then(resp => { + expect(resp.data.source).to.have.property('logItem'); + const { logItem } = resp.data.source; + if (!logItem) { + throw new Error('Log item should not be falsey'); + } + expect(logItem).to.have.property('id', 'yT2Mg2YBh-opCxJv8Vqj'); + expect(logItem).to.have.property('index', 'filebeat-7.0.0-alpha1-2018.10.17'); + expect(logItem).to.have.property('fields'); + expect(logItem.fields).to.eql([ + { + field: '@timestamp', + value: '2018-10-17T19:42:22.000Z', + __typename: 'InfraLogItemField', + }, + { + field: '_id', + value: 'yT2Mg2YBh-opCxJv8Vqj', + __typename: 'InfraLogItemField', + }, + { + field: '_index', + value: 'filebeat-7.0.0-alpha1-2018.10.17', + __typename: 'InfraLogItemField', + }, + { + field: 'apache2.access.body_sent.bytes', + value: '1336', + __typename: 'InfraLogItemField', + }, + { + field: 'apache2.access.http_version', + value: '1.1', + __typename: 'InfraLogItemField', + }, + { + field: 'apache2.access.method', + value: 'GET', + __typename: 'InfraLogItemField', + }, + { + field: 'apache2.access.referrer', + value: '-', + __typename: 'InfraLogItemField', + }, + { + field: 'apache2.access.remote_ip', + value: '10.128.0.11', + __typename: 'InfraLogItemField', + }, + { + field: 'apache2.access.response_code', + value: '200', + __typename: 'InfraLogItemField', + }, + { + field: 'apache2.access.url', + value: '/a-fresh-start-will-put-you-on-your-way', + __typename: 'InfraLogItemField', + }, + { + field: 'apache2.access.user_agent.device', + value: 'Other', + __typename: 'InfraLogItemField', + }, + { + field: 'apache2.access.user_agent.name', + value: 'Other', + __typename: 'InfraLogItemField', + }, + { + field: 'apache2.access.user_agent.os', + value: 'Other', + __typename: 'InfraLogItemField', + }, + { + field: 'apache2.access.user_agent.os_name', + value: 'Other', + __typename: 'InfraLogItemField', + }, + { + field: 'apache2.access.user_name', + value: '-', + __typename: 'InfraLogItemField', + }, + { + field: 'beat.hostname', + value: 'demo-stack-apache-01', + __typename: 'InfraLogItemField', + }, + { + field: 'beat.name', + value: 'demo-stack-apache-01', + __typename: 'InfraLogItemField', + }, + { + field: 'beat.version', + value: '7.0.0-alpha1', + __typename: 'InfraLogItemField', + }, + { + field: 'fileset.module', + value: 'apache2', + __typename: 'InfraLogItemField', + }, + { + field: 'fileset.name', + value: 'access', + __typename: 'InfraLogItemField', + }, + { + field: 'host.name', + value: 'demo-stack-apache-01', + __typename: 'InfraLogItemField', + }, + { + field: 'input.type', + value: 'log', + __typename: 'InfraLogItemField', + }, + { + field: 'offset', + value: '5497614', + __typename: 'InfraLogItemField', + }, + { + field: 'prospector.type', + value: 'log', + __typename: 'InfraLogItemField', + }, + { + field: 'read_timestamp', + value: '2018-10-17T19:42:23.160Z', + __typename: 'InfraLogItemField', + }, + { + field: 'source', + value: '/var/log/apache2/access.log', + __typename: 'InfraLogItemField', + }, + ]); + }); + }); + }); +}; + +// tslint:disable-next-line no-default-export +export default logItemTests; diff --git a/x-pack/test/api_integration/apis/infra/waffle.ts b/x-pack/test/api_integration/apis/infra/waffle.ts index 742e980230430..9efdbfdde119a 100644 --- a/x-pack/test/api_integration/apis/infra/waffle.ts +++ b/x-pack/test/api_integration/apis/infra/waffle.ts @@ -48,6 +48,8 @@ const waffleTests: KbnTestProvider = ({ getService }) => { expect(firstNode.metric).to.eql({ name: 'cpu', value: 0.011, + avg: 0.012215686274509805, + max: 0.020999999999999998, __typename: 'InfraNodeMetric', }); } diff --git a/x-pack/test/functional/apps/canvas/smoke_test.js b/x-pack/test/functional/apps/canvas/smoke_test.js index e94001a6e0508..45552e1a4067d 100644 --- a/x-pack/test/functional/apps/canvas/smoke_test.js +++ b/x-pack/test/functional/apps/canvas/smoke_test.js @@ -46,8 +46,10 @@ export default function canvasSmokeTest({ getService, getPageObjects }) { await retry.waitFor('workpad page', () => testSubjects.exists('canvasWorkpadPage')); // check that workpad loaded in url - const url = await browser.getCurrentUrl(); - expect(parse(url).hash).to.equal(`#/workpad/${testWorkpadId}/page/1`); + await retry.try(async () => { + const url = await browser.getCurrentUrl(); + expect(parse(url).hash).to.equal(`#/workpad/${testWorkpadId}/page/1`); + }); }); it('renders elements on workpad', async () => { diff --git a/x-pack/test/functional/apps/gis/saved_object_management.js b/x-pack/test/functional/apps/gis/saved_object_management.js index bbfb6c7a2dd24..6ad4415bb423d 100644 --- a/x-pack/test/functional/apps/gis/saved_object_management.js +++ b/x-pack/test/functional/apps/gis/saved_object_management.js @@ -8,7 +8,7 @@ import expect from 'expect.js'; export default function ({ getPageObjects, getService }) { - const PageObjects = getPageObjects(['gis', 'header']); + const PageObjects = getPageObjects(['gis', 'header', 'timePicker']); const queryBar = getService('queryBar'); const browser = getService('browser'); const inspector = getService('inspector'); @@ -25,13 +25,16 @@ export default function ({ getPageObjects, getService }) { }); it('should update global Kibana time to value stored with map', async () => { - const kibanaTime = await PageObjects.header.getPrettyDuration(); - expect(kibanaTime).to.equal('Last 17m'); + const timeConfig = await PageObjects.timePicker.getTimeConfig(); + expect(timeConfig.start).to.equal('~ 17 minutes ago'); + expect(timeConfig.end).to.equal('now'); }); it('should update global Kibana refresh config to value stored with map', async () => { - const kibanaRefreshConfig = await PageObjects.header.getRefreshConfig(); - expect(kibanaRefreshConfig).to.equal('inactive 1 second'); + const kibanaRefreshConfig = await PageObjects.timePicker.getRefreshConfig(); + expect(kibanaRefreshConfig.interval).to.equal('0.02'); + expect(kibanaRefreshConfig.units).to.equal('minutes'); + expect(kibanaRefreshConfig.isPaused).to.equal(true); }); it('should set map location to value stored with map', async () => { diff --git a/x-pack/test/functional/apps/spaces/spaces_selection.ts b/x-pack/test/functional/apps/spaces/spaces_selection.ts index be2b59d69d864..ddd98890c46b3 100644 --- a/x-pack/test/functional/apps/spaces/spaces_selection.ts +++ b/x-pack/test/functional/apps/spaces/spaces_selection.ts @@ -7,8 +7,16 @@ import { TestInvoker } from './lib/types'; // tslint:disable:no-default-export export default function spaceSelectorFunctonalTests({ getService, getPageObjects }: TestInvoker) { + const config = getService('config'); const esArchiver = getService('esArchiver'); - const PageObjects = getPageObjects(['security', 'spaceSelector', 'home']); + const PageObjects = getPageObjects([ + 'common', + 'dashboard', + 'header', + 'home', + 'security', + 'spaceSelector', + ]); describe('Spaces', () => { describe('Space Selector', () => { @@ -39,5 +47,74 @@ export default function spaceSelectorFunctonalTests({ getService, getPageObjects await PageObjects.spaceSelector.expectHomePage('default'); }); }); + + describe('Spaces Data', () => { + const spaceId = 'another-space'; + const dashboardPath = config.get(['apps', 'dashboard']).pathname; + const homePath = config.get(['apps', 'home']).pathname; + const sampleDataHash = '/home/tutorial_directory/sampleData'; + + const expectDashboardRenders = async (dashName: string) => { + await PageObjects.dashboard.searchForDashboardWithName(dashName); + await PageObjects.dashboard.selectDashboard(dashName); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.dashboard.waitForRenderComplete(); // throws if all items are not rendered + }; + + before(async () => { + await esArchiver.load('spaces'); + await PageObjects.security.login(null, null, { + expectSpaceSelector: true, + }); + await PageObjects.spaceSelector.clickSpaceCard('default'); + await PageObjects.common.navigateToApp('home', { + appConfig: { + hash: sampleDataHash, + }, + }); + await PageObjects.home.addSampleDataSet('logs'); + await PageObjects.common.navigateToApp('home', { + appConfig: { + hash: sampleDataHash, + pathname: `/s/${spaceId}${homePath}`, + }, + }); + await PageObjects.home.addSampleDataSet('flights'); + }); + + after(async () => { + await PageObjects.common.navigateToApp('home', { + appConfig: { + hash: sampleDataHash, + }, + }); + await PageObjects.home.removeSampleDataSet('logs'); + await PageObjects.common.navigateToApp('home', { + appConfig: { + hash: sampleDataHash, + pathname: `/s/${spaceId}${homePath}`, + }, + }); + await PageObjects.home.removeSampleDataSet('flights'); + await PageObjects.security.logout(); + await esArchiver.unload('spaces'); + }); + + describe('displays separate data for each space', async () => { + it('in the default space', async () => { + await PageObjects.common.navigateToApp('dashboard'); + await expectDashboardRenders('[Logs] Web Traffic'); + }); + + it('in a custom space', async () => { + await PageObjects.common.navigateToApp('dashboard', { + appConfig: { + pathname: `/s/${spaceId}${dashboardPath}`, + }, + }); + await expectDashboardRenders('[Flights] Global Flight Dashboard'); + }); + }); + }); }); } diff --git a/x-pack/test/functional/es_archives/empty_kibana/data.json.gz b/x-pack/test/functional/es_archives/empty_kibana/data.json.gz index 2aa6460986df4..1fd039ebc7c64 100644 Binary files a/x-pack/test/functional/es_archives/empty_kibana/data.json.gz and b/x-pack/test/functional/es_archives/empty_kibana/data.json.gz differ diff --git a/x-pack/test/functional/es_archives/infra/logs_without_epoch_millis/data.json.gz b/x-pack/test/functional/es_archives/infra/logs_without_epoch_millis/data.json.gz index b07c21c155075..80eb707ec1aa7 100644 Binary files a/x-pack/test/functional/es_archives/infra/logs_without_epoch_millis/data.json.gz and b/x-pack/test/functional/es_archives/infra/logs_without_epoch_millis/data.json.gz differ diff --git a/x-pack/test/functional/es_archives/infra/logs_without_epoch_millis/mappings.json b/x-pack/test/functional/es_archives/infra/logs_without_epoch_millis/mappings.json index 7b3c6e167437e..185d1d72b361b 100644 --- a/x-pack/test/functional/es_archives/infra/logs_without_epoch_millis/mappings.json +++ b/x-pack/test/functional/es_archives/infra/logs_without_epoch_millis/mappings.json @@ -1708,4 +1708,4 @@ }, "aliases": {} } -} \ No newline at end of file +} diff --git a/x-pack/test/functional/es_archives/infra/metrics_and_logs/data.json.gz b/x-pack/test/functional/es_archives/infra/metrics_and_logs/data.json.gz index f212df30f74ba..a124024b2d100 100644 Binary files a/x-pack/test/functional/es_archives/infra/metrics_and_logs/data.json.gz and b/x-pack/test/functional/es_archives/infra/metrics_and_logs/data.json.gz differ diff --git a/x-pack/test/functional/es_archives/infra/metrics_and_logs/mappings.json b/x-pack/test/functional/es_archives/infra/metrics_and_logs/mappings.json index 353c8102faf28..25169a6fcb56c 100644 --- a/x-pack/test/functional/es_archives/infra/metrics_and_logs/mappings.json +++ b/x-pack/test/functional/es_archives/infra/metrics_and_logs/mappings.json @@ -10745,4 +10745,4 @@ }, "aliases": {} } -} \ No newline at end of file +} diff --git a/x-pack/test/functional/page_objects/gis_page.js b/x-pack/test/functional/page_objects/gis_page.js index 0ee327e984c87..75a5bf22b800b 100644 --- a/x-pack/test/functional/page_objects/gis_page.js +++ b/x-pack/test/functional/page_objects/gis_page.js @@ -5,7 +5,7 @@ */ export function GisPageProvider({ getService, getPageObjects }) { - const PageObjects = getPageObjects(['common', 'header']); + const PageObjects = getPageObjects(['common', 'header', 'timePicker']); const log = getService('log'); const testSubjects = getService('testSubjects'); @@ -204,10 +204,10 @@ export function GisPageProvider({ getService, getPageObjects }) { async triggerSingleRefresh(refreshInterval) { log.debug(`triggerSingleRefresh, refreshInterval: ${refreshInterval}`); - await PageObjects.header.resumeAutoRefresh(); + await PageObjects.timePicker.resumeAutoRefresh(); log.debug('waiting to give time for refresh timer to fire'); await PageObjects.common.sleep(refreshInterval + (refreshInterval / 2)); - await PageObjects.header.pauseAutoRefresh(); + await PageObjects.timePicker.pauseAutoRefresh(); await PageObjects.header.waitUntilLoadingHasFinished(); } } diff --git a/x-pack/test/functional/page_objects/space_selector_page.js b/x-pack/test/functional/page_objects/space_selector_page.js index f04902c240a1c..9f7ff9ced6b6d 100644 --- a/x-pack/test/functional/page_objects/space_selector_page.js +++ b/x-pack/test/functional/page_objects/space_selector_page.js @@ -12,7 +12,7 @@ export function SpaceSelectorPageProvider({ getService, getPageObjects }) { const testSubjects = getService('testSubjects'); const browser = getService('browser'); const find = getService('find'); - const PageObjects = getPageObjects(['common', 'home', 'security']); + const PageObjects = getPageObjects(['common', 'header', 'security']); class SpaceSelectorPage { async initTests() { diff --git a/x-pack/test/saved_object_api_integration/common/fixtures/es_archiver/saved_objects/spaces/data.json b/x-pack/test/saved_object_api_integration/common/fixtures/es_archiver/saved_objects/spaces/data.json index 6bf3413f3797d..368384479bc30 100644 --- a/x-pack/test/saved_object_api_integration/common/fixtures/es_archiver/saved_objects/spaces/data.json +++ b/x-pack/test/saved_object_api_integration/common/fixtures/es_archiver/saved_objects/spaces/data.json @@ -195,7 +195,7 @@ "description": "", "version": 1, "kibanaSavedObjectMeta": { - "searchSourceJSON": "{\"index\":\"91200a00-9efd-11e7-acb3-3dab96693fab\",\"filter\":[],\"query\":{\"query\":\"\",\"language\":\"lucene\"}}" + "searchSourceJSON": "{\"index\":\"space_1-91200a00-9efd-11e7-acb3-3dab96693fab\",\"filter\":[],\"query\":{\"query\":\"\",\"language\":\"lucene\"}}" } } } @@ -216,7 +216,7 @@ "title": "Requests", "hits": 0, "description": "", - "panelsJSON": "[{\"size_x\":6,\"size_y\":3,\"panelIndex\":1,\"type\":\"visualization\",\"id\":\"dd7caf20-9efd-11e7-acb3-3dab96693fab\",\"col\":1,\"row\":1}]", + "panelsJSON": "[{\"size_x\":6,\"size_y\":3,\"panelIndex\":1,\"type\":\"visualization\",\"id\":\"space_1-dd7caf20-9efd-11e7-acb3-3dab96693fab\",\"col\":1,\"row\":1}]", "optionsJSON": "{}", "uiStateJSON": "{}", "version": 1, @@ -291,7 +291,7 @@ "description": "", "version": 1, "kibanaSavedObjectMeta": { - "searchSourceJSON": "{\"index\":\"91200a00-9efd-11e7-acb3-3dab96693fab\",\"filter\":[],\"query\":{\"query\":\"\",\"language\":\"lucene\"}}" + "searchSourceJSON": "{\"index\":\"space_2-91200a00-9efd-11e7-acb3-3dab96693fab\",\"filter\":[],\"query\":{\"query\":\"\",\"language\":\"lucene\"}}" } } } @@ -312,7 +312,7 @@ "title": "Requests", "hits": 0, "description": "", - "panelsJSON": "[{\"size_x\":6,\"size_y\":3,\"panelIndex\":1,\"type\":\"visualization\",\"id\":\"dd7caf20-9efd-11e7-acb3-3dab96693fab\",\"col\":1,\"row\":1}]", + "panelsJSON": "[{\"size_x\":6,\"size_y\":3,\"panelIndex\":1,\"type\":\"visualization\",\"id\":\"space_2-dd7caf20-9efd-11e7-acb3-3dab96693fab\",\"col\":1,\"row\":1}]", "optionsJSON": "{}", "uiStateJSON": "{}", "version": 1, diff --git a/x-pack/test/saved_object_api_integration/common/suites/bulk_create.ts b/x-pack/test/saved_object_api_integration/common/suites/bulk_create.ts index 0d9e800c64003..6dcaae760a58d 100644 --- a/x-pack/test/saved_object_api_integration/common/suites/bulk_create.ts +++ b/x-pack/test/saved_object_api_integration/common/suites/bulk_create.ts @@ -88,6 +88,7 @@ export function bulkCreateTestSuiteFactory(es: any, esArchiver: any, supertest: attributes: { title: 'A great new dashboard', }, + references: [], }, { type: 'globaltype', @@ -97,6 +98,7 @@ export function bulkCreateTestSuiteFactory(es: any, esArchiver: any, supertest: attributes: { name: 'A new globaltype object', }, + references: [], }, { type: 'globaltype', diff --git a/x-pack/test/saved_object_api_integration/common/suites/bulk_get.ts b/x-pack/test/saved_object_api_integration/common/suites/bulk_get.ts index fdffe545f3416..e82ea7b856a8c 100644 --- a/x-pack/test/saved_object_api_integration/common/suites/bulk_get.ts +++ b/x-pack/test/saved_object_api_integration/common/suites/bulk_get.ts @@ -69,6 +69,7 @@ export function bulkGetTestSuiteFactory(esArchiver: any, supertest: SuperTest) attributes: { name: 'My favorite global object', }, + references: [], }, ], }); @@ -99,6 +100,7 @@ export function findTestSuiteFactory(esArchiver: any, supertest: SuperTest) attributes: { title: 'Count of requests', }, + references: [], }, ], }); diff --git a/x-pack/test/saved_object_api_integration/common/suites/get.ts b/x-pack/test/saved_object_api_integration/common/suites/get.ts index 5bf2385544a7d..1cc36b411c61a 100644 --- a/x-pack/test/saved_object_api_integration/common/suites/get.ts +++ b/x-pack/test/saved_object_api_integration/common/suites/get.ts @@ -69,6 +69,7 @@ export function getTestSuiteFactory(esArchiver: any, supertest: SuperTest) attributes: { name: 'My favorite global object', }, + references: [], }); }; @@ -108,6 +109,13 @@ export function getTestSuiteFactory(esArchiver: any, supertest: SuperTest) uiStateJSON: resp.body.attributes.uiStateJSON, kibanaSavedObjectMeta: resp.body.attributes.kibanaSavedObjectMeta, }, + references: [ + { + name: 'kibanaSavedObjectMeta.searchSourceJSON.index', + type: 'index-pattern', + id: `${getIdPrefix(spaceId)}91200a00-9efd-11e7-acb3-3dab96693fab`, + }, + ], }); }; diff --git a/x-pack/test/saved_object_api_integration/common/suites/update.ts b/x-pack/test/saved_object_api_integration/common/suites/update.ts index f936567200f54..5d496ba58bbba 100644 --- a/x-pack/test/saved_object_api_integration/common/suites/update.ts +++ b/x-pack/test/saved_object_api_integration/common/suites/update.ts @@ -78,6 +78,7 @@ export function updateTestSuiteFactory(esArchiver: any, supertest: SuperTest