diff --git a/.ci/packer_cache.sh b/.ci/packer_cache.sh index b697f22c009d..ab68a60dcfc2 100755 --- a/.ci/packer_cache.sh +++ b/.ci/packer_cache.sh @@ -44,6 +44,7 @@ tar -cf "$HOME/.kibana/bootstrap_cache/$branch.tar" \ x-pack/legacy/plugins/*/node_modules \ x-pack/legacy/plugins/reporting/.chromium \ test/plugin_functional/plugins/*/node_modules \ + examples/*/node_modules \ .es \ .chromedriver \ .geckodriver; diff --git a/.eslintrc.js b/.eslintrc.js index 106724c323d3..367ac892107a 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -170,13 +170,6 @@ module.exports = { 'react-hooks/rules-of-hooks': 'off', }, }, - { - files: ['x-pack/legacy/plugins/infra/**/*.{js,ts,tsx}'], - rules: { - 'react-hooks/exhaustive-deps': 'off', - 'react-hooks/rules-of-hooks': 'off', - }, - }, { files: ['x-pack/legacy/plugins/lens/**/*.{js,ts,tsx}'], rules: { @@ -209,13 +202,6 @@ module.exports = { 'react-hooks/rules-of-hooks': 'off', }, }, - { - files: ['x-pack/legacy/plugins/watcher/**/*.{js,ts,tsx}'], - rules: { - 'react-hooks/rules-of-hooks': 'off', - 'react-hooks/exhaustive-deps': 'off', - }, - }, /** * Prettier diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index c5e6768c17d4..338fbf2e359b 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -5,6 +5,8 @@ # App /x-pack/legacy/plugins/lens/ @elastic/kibana-app /x-pack/legacy/plugins/graph/ @elastic/kibana-app +/src/plugins/share/ @elastic/kibana-app +/src/legacy/server/url_shortening/ @elastic/kibana-app /src/legacy/server/sample_data/ @elastic/kibana-app # App Architecture @@ -14,7 +16,6 @@ /src/plugins/kibana_react/ @elastic/kibana-app-arch /src/plugins/kibana_utils/ @elastic/kibana-app-arch /src/plugins/navigation/ @elastic/kibana-app-arch -/src/plugins/share/ @elastic/kibana-app-arch /src/plugins/ui_actions/ @elastic/kibana-app-arch /src/plugins/visualizations/ @elastic/kibana-app-arch /x-pack/plugins/advanced_ui_actions/ @elastic/kibana-app-arch @@ -28,7 +29,6 @@ /src/legacy/core_plugins/kibana/server/routes/api/suggestions/ @elastic/kibana-app-arch /src/legacy/core_plugins/visualizations/ @elastic/kibana-app-arch /src/legacy/server/index_patterns/ @elastic/kibana-app-arch -/src/legacy/server/url_shortening/ @elastic/kibana-app-arch # APM /x-pack/legacy/plugins/apm/ @elastic/apm-ui diff --git a/docs/setup/settings.asciidoc b/docs/setup/settings.asciidoc index 39c87d97af4b..5cda7b2b214f 100644 --- a/docs/setup/settings.asciidoc +++ b/docs/setup/settings.asciidoc @@ -65,6 +65,8 @@ connects to this Kibana instance. `elasticsearch.requestHeadersWhitelist:`:: *Default: `[ 'authorization' ]`* List of Kibana client-side headers to send to Elasticsearch. To send *no* client-side headers, set this value to [] (an empty list). +Removing the `authorization` header from being whitelisted means that you cannot +use <> in Kibana. `elasticsearch.requestTimeout:`:: *Default: 30000* Time in milliseconds to wait for responses from the back end or Elasticsearch. This value must be a positive diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 000000000000..7cade0b35f82 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,8 @@ +## Example plugins + +This folder contains example plugins. To run the plugins in this folder, use the `--run-examples` flag, via + +``` +yarn start --run-examples +``` + diff --git a/examples/demo_search/README.md b/examples/demo_search/README.md new file mode 100644 index 000000000000..f0b461e3287b --- /dev/null +++ b/examples/demo_search/README.md @@ -0,0 +1,8 @@ +## Demo search strategy + +This example registers a custom search strategy that simply takes a name string in the request and returns the +string `Hello {name}` + +To see the demo search strategy in action, navigate to the `Search explorer` app. + +To run these examples, use the command `yarn start --run-examples`. \ No newline at end of file diff --git a/test/plugin_functional/plugins/demo_search/common/index.ts b/examples/demo_search/common/index.ts similarity index 90% rename from test/plugin_functional/plugins/demo_search/common/index.ts rename to examples/demo_search/common/index.ts index 9254412ece29..6587ee96ef61 100644 --- a/test/plugin_functional/plugins/demo_search/common/index.ts +++ b/examples/demo_search/common/index.ts @@ -17,10 +17,7 @@ * under the License. */ -import { - IKibanaSearchRequest, - IKibanaSearchResponse, -} from '../../../../../src/plugins/data/public'; +import { IKibanaSearchRequest, IKibanaSearchResponse } from '../../../src/plugins/data/public'; export const DEMO_SEARCH_STRATEGY = 'DEMO_SEARCH_STRATEGY'; diff --git a/test/plugin_functional/plugins/demo_search/kibana.json b/examples/demo_search/kibana.json similarity index 100% rename from test/plugin_functional/plugins/demo_search/kibana.json rename to examples/demo_search/kibana.json diff --git a/test/plugin_functional/plugins/demo_search/package.json b/examples/demo_search/package.json similarity index 88% rename from test/plugin_functional/plugins/demo_search/package.json rename to examples/demo_search/package.json index 1f4fa1421906..404002a50e71 100644 --- a/test/plugin_functional/plugins/demo_search/package.json +++ b/examples/demo_search/package.json @@ -8,7 +8,7 @@ }, "license": "Apache-2.0", "scripts": { - "kbn": "node ../../../../scripts/kbn.js", + "kbn": "node ../../scripts/kbn.js", "build": "rm -rf './target' && tsc" }, "devDependencies": { diff --git a/test/plugin_functional/plugins/demo_search/public/demo_search_strategy.ts b/examples/demo_search/public/demo_search_strategy.ts similarity index 96% rename from test/plugin_functional/plugins/demo_search/public/demo_search_strategy.ts rename to examples/demo_search/public/demo_search_strategy.ts index 298eaaaf420e..d2854151e14c 100644 --- a/test/plugin_functional/plugins/demo_search/public/demo_search_strategy.ts +++ b/examples/demo_search/public/demo_search_strategy.ts @@ -22,8 +22,8 @@ import { ISearchContext, SYNC_SEARCH_STRATEGY, ISearchGeneric, -} from '../../../../../src/plugins/data/public'; -import { TSearchStrategyProvider, ISearchStrategy } from '../../../../../src/plugins/data/public'; +} from '../../../src/plugins/data/public'; +import { TSearchStrategyProvider, ISearchStrategy } from '../../../src/plugins/data/public'; import { DEMO_SEARCH_STRATEGY, IDemoResponse } from '../common'; diff --git a/test/plugin_functional/plugins/demo_search/public/index.ts b/examples/demo_search/public/index.ts similarity index 100% rename from test/plugin_functional/plugins/demo_search/public/index.ts rename to examples/demo_search/public/index.ts diff --git a/test/plugin_functional/plugins/demo_search/public/plugin.ts b/examples/demo_search/public/plugin.ts similarity index 92% rename from test/plugin_functional/plugins/demo_search/public/plugin.ts rename to examples/demo_search/public/plugin.ts index 37f8d3955708..81ba585b9919 100644 --- a/test/plugin_functional/plugins/demo_search/public/plugin.ts +++ b/examples/demo_search/public/plugin.ts @@ -17,8 +17,8 @@ * under the License. */ -import { DataPublicPluginSetup } from '../../../../../src/plugins/data/public'; -import { Plugin, CoreSetup, PluginInitializerContext } from '../../../../../src/core/public'; +import { DataPublicPluginSetup } from '../../../src/plugins/data/public'; +import { Plugin, CoreSetup, PluginInitializerContext } from '../../../src/core/public'; import { DEMO_SEARCH_STRATEGY } from '../common'; import { demoClientSearchStrategyProvider } from './demo_search_strategy'; import { IDemoRequest, IDemoResponse } from '../common'; @@ -36,7 +36,7 @@ interface DemoDataSearchSetupDependencies { * If the caller does not pass in the right `request` shape, typescript will * complain. The caller will also get a typed response. */ -declare module '../../../../../src/plugins/data/public' { +declare module '../../../src/plugins/data/public' { export interface IRequestTypesMap { [DEMO_SEARCH_STRATEGY]: IDemoRequest; } diff --git a/test/plugin_functional/plugins/demo_search/server/demo_search_strategy.ts b/examples/demo_search/server/demo_search_strategy.ts similarity index 94% rename from test/plugin_functional/plugins/demo_search/server/demo_search_strategy.ts rename to examples/demo_search/server/demo_search_strategy.ts index d3f2360add6c..5b0883be1fc5 100644 --- a/test/plugin_functional/plugins/demo_search/server/demo_search_strategy.ts +++ b/examples/demo_search/server/demo_search_strategy.ts @@ -17,7 +17,7 @@ * under the License. */ -import { TSearchStrategyProvider } from 'src/plugins/data/server'; +import { TSearchStrategyProvider } from '../../../src/plugins/data/server'; import { DEMO_SEARCH_STRATEGY } from '../common'; export const demoSearchStrategyProvider: TSearchStrategyProvider = () => { diff --git a/test/plugin_functional/plugins/demo_search/server/index.ts b/examples/demo_search/server/index.ts similarity index 100% rename from test/plugin_functional/plugins/demo_search/server/index.ts rename to examples/demo_search/server/index.ts diff --git a/test/plugin_functional/plugins/demo_search/server/plugin.ts b/examples/demo_search/server/plugin.ts similarity index 97% rename from test/plugin_functional/plugins/demo_search/server/plugin.ts rename to examples/demo_search/server/plugin.ts index c6628e7c7682..23c82225563c 100644 --- a/test/plugin_functional/plugins/demo_search/server/plugin.ts +++ b/examples/demo_search/server/plugin.ts @@ -35,7 +35,7 @@ interface IDemoSearchExplorerDeps { * If the caller does not pass in the right `request` shape, typescript will * complain. The caller will also get a typed response. */ -declare module '../../../../../src/plugins/data/server' { +declare module '../../../src/plugins/data/server' { export interface IRequestTypesMap { [DEMO_SEARCH_STRATEGY]: IDemoRequest; } diff --git a/test/plugin_functional/plugins/demo_search/tsconfig.json b/examples/demo_search/tsconfig.json similarity index 75% rename from test/plugin_functional/plugins/demo_search/tsconfig.json rename to examples/demo_search/tsconfig.json index 304ffdc0a299..7fa03739119b 100644 --- a/test/plugin_functional/plugins/demo_search/tsconfig.json +++ b/examples/demo_search/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../../../tsconfig.json", + "extends": "../../tsconfig.json", "compilerOptions": { "outDir": "./target", "skipLibCheck": true @@ -10,7 +10,7 @@ "public/**/*.ts", "public/**/*.tsx", "server/**/*.ts", - "../../../../typings/**/*" + "../../typings/**/*" ], "exclude": [] } diff --git a/examples/search_explorer/README.md b/examples/search_explorer/README.md new file mode 100644 index 000000000000..0e5a48cf22dc --- /dev/null +++ b/examples/search_explorer/README.md @@ -0,0 +1,8 @@ +## Search explorer + +This example search explorer app shows how to use different search strategies in order to retrieve data. + +One demo uses the built in elasticsearch search strategy, and runs a search against data in elasticsearch. The +other demo uses the custom demo search strategy, a custom search strategy registerd inside the [demo_search plugin](../demo_search). + +To run this example, use the command `yarn start --run-examples`. \ No newline at end of file diff --git a/test/plugin_functional/plugins/search_explorer/kibana.json b/examples/search_explorer/kibana.json similarity index 100% rename from test/plugin_functional/plugins/search_explorer/kibana.json rename to examples/search_explorer/kibana.json diff --git a/test/plugin_functional/plugins/search_explorer/package.json b/examples/search_explorer/package.json similarity index 87% rename from test/plugin_functional/plugins/search_explorer/package.json rename to examples/search_explorer/package.json index 9a5e0e83a220..62d0127c30cc 100644 --- a/test/plugin_functional/plugins/search_explorer/package.json +++ b/examples/search_explorer/package.json @@ -8,7 +8,7 @@ }, "license": "Apache-2.0", "scripts": { - "kbn": "node ../../../../scripts/kbn.js", + "kbn": "node ../../scripts/kbn.js", "build": "rm -rf './target' && tsc" }, "devDependencies": { diff --git a/test/plugin_functional/plugins/search_explorer/public/application.tsx b/examples/search_explorer/public/application.tsx similarity index 97% rename from test/plugin_functional/plugins/search_explorer/public/application.tsx rename to examples/search_explorer/public/application.tsx index 4762209a548c..801a3c615ac6 100644 --- a/test/plugin_functional/plugins/search_explorer/public/application.tsx +++ b/examples/search_explorer/public/application.tsx @@ -28,7 +28,7 @@ import { EuiSideNav, } from '@elastic/eui'; -import { AppMountContext, AppMountParameters } from '../../../../../src/core/public'; +import { AppMountContext, AppMountParameters } from '../../../src/core/public'; import { EsSearchTest } from './es_strategy'; import { Page } from './page'; import { DemoStrategy } from './demo_strategy'; diff --git a/test/plugin_functional/plugins/search_explorer/public/demo_strategy.tsx b/examples/search_explorer/public/demo_strategy.tsx similarity index 98% rename from test/plugin_functional/plugins/search_explorer/public/demo_strategy.tsx rename to examples/search_explorer/public/demo_strategy.tsx index 8a0dd31e3595..7c6c21d2b7ae 100644 --- a/test/plugin_functional/plugins/search_explorer/public/demo_strategy.tsx +++ b/examples/search_explorer/public/demo_strategy.tsx @@ -25,7 +25,7 @@ import { EuiFlexGroup, EuiFieldText, } from '@elastic/eui'; -import { ISearchGeneric } from '../../../../../src/plugins/data/public'; +import { ISearchGeneric } from '../../../src/plugins/data/public'; import { DoSearch } from './do_search'; import { GuideSection } from './guide_section'; diff --git a/test/plugin_functional/plugins/search_explorer/public/do_search.tsx b/examples/search_explorer/public/do_search.tsx similarity index 97% rename from test/plugin_functional/plugins/search_explorer/public/do_search.tsx rename to examples/search_explorer/public/do_search.tsx index e039e4ff3f63..f279b9fcd6e2 100644 --- a/test/plugin_functional/plugins/search_explorer/public/do_search.tsx +++ b/examples/search_explorer/public/do_search.tsx @@ -21,10 +21,7 @@ import React from 'react'; import { EuiButton, EuiCodeBlock, EuiFlexItem, EuiFlexGroup, EuiText } from '@elastic/eui'; import { EuiProgress } from '@elastic/eui'; import { Observable } from 'rxjs'; -import { - IKibanaSearchResponse, - IKibanaSearchRequest, -} from '../../../../../src/plugins/data/public'; +import { IKibanaSearchResponse, IKibanaSearchRequest } from '../../../src/plugins/data/public'; interface Props { request: IKibanaSearchRequest; diff --git a/test/plugin_functional/plugins/search_explorer/public/documentation.tsx b/examples/search_explorer/public/documentation.tsx similarity index 100% rename from test/plugin_functional/plugins/search_explorer/public/documentation.tsx rename to examples/search_explorer/public/documentation.tsx diff --git a/test/plugin_functional/plugins/search_explorer/public/es_strategy.tsx b/examples/search_explorer/public/es_strategy.tsx similarity index 87% rename from test/plugin_functional/plugins/search_explorer/public/es_strategy.tsx rename to examples/search_explorer/public/es_strategy.tsx index d35c67191a1f..e26c11a64666 100644 --- a/test/plugin_functional/plugins/search_explorer/public/es_strategy.tsx +++ b/examples/search_explorer/public/es_strategy.tsx @@ -29,19 +29,19 @@ import { ISearchGeneric, IEsSearchResponse, IEsSearchRequest, -} from '../../../../../src/plugins/data/public'; +} from '../../../src/plugins/data/public'; import { DoSearch } from './do_search'; import { GuideSection } from './guide_section'; // @ts-ignore -import serverPlugin from '!!raw-loader!./../../../../../src/plugins/data/server/search/es_search/es_search_service'; +import serverPlugin from '!!raw-loader!./../../../src/plugins/data/server/search/es_search/es_search_service'; // @ts-ignore -import serverStrategy from '!!raw-loader!./../../../../../src/plugins/data/server/search/es_search/es_search_strategy'; +import serverStrategy from '!!raw-loader!./../../../src/plugins/data/server/search/es_search/es_search_strategy'; // @ts-ignore -import publicPlugin from '!!raw-loader!./../../../../../src/plugins/data/public/search/es_search/es_search_service'; +import publicPlugin from '!!raw-loader!./../../../src/plugins/data/public/search/es_search/es_search_service'; // @ts-ignore -import publicStrategy from '!!raw-loader!./../../../../../src/plugins/data/public/search/es_search/es_search_strategy'; +import publicStrategy from '!!raw-loader!./../../../src/plugins/data/public/search/es_search/es_search_strategy'; interface Props { search: ISearchGeneric; diff --git a/test/plugin_functional/plugins/search_explorer/public/guide_section.tsx b/examples/search_explorer/public/guide_section.tsx similarity index 100% rename from test/plugin_functional/plugins/search_explorer/public/guide_section.tsx rename to examples/search_explorer/public/guide_section.tsx diff --git a/test/plugin_functional/plugins/search_explorer/public/index.ts b/examples/search_explorer/public/index.ts similarity index 100% rename from test/plugin_functional/plugins/search_explorer/public/index.ts rename to examples/search_explorer/public/index.ts diff --git a/test/plugin_functional/plugins/search_explorer/public/page.tsx b/examples/search_explorer/public/page.tsx similarity index 100% rename from test/plugin_functional/plugins/search_explorer/public/page.tsx rename to examples/search_explorer/public/page.tsx diff --git a/test/plugin_functional/plugins/search_explorer/public/plugin.tsx b/examples/search_explorer/public/plugin.tsx similarity index 94% rename from test/plugin_functional/plugins/search_explorer/public/plugin.tsx rename to examples/search_explorer/public/plugin.tsx index cbe1073aa186..a7a6fd11341a 100644 --- a/test/plugin_functional/plugins/search_explorer/public/plugin.tsx +++ b/examples/search_explorer/public/plugin.tsx @@ -18,7 +18,7 @@ */ import { Plugin, CoreSetup } from 'kibana/public'; -import { ISearchAppMountContext } from '../../../../../src/plugins/data/public'; +import { ISearchAppMountContext } from '../../../src/plugins/data/public'; declare module 'kibana/public' { interface AppMountContext { diff --git a/test/plugin_functional/plugins/search_explorer/public/search_api.tsx b/examples/search_explorer/public/search_api.tsx similarity index 70% rename from test/plugin_functional/plugins/search_explorer/public/search_api.tsx rename to examples/search_explorer/public/search_api.tsx index 8ec6225d1f17..fc68571e4ef6 100644 --- a/test/plugin_functional/plugins/search_explorer/public/search_api.tsx +++ b/examples/search_explorer/public/search_api.tsx @@ -20,22 +20,22 @@ import React from 'react'; import { GuideSection } from './guide_section'; // @ts-ignore -import publicSetupContract from '!!raw-loader!./../../../../../src/plugins/data/public/search/i_search_setup'; +import publicSetupContract from '!!raw-loader!./../../../src/plugins/data/public/search/i_search_setup'; // @ts-ignore -import publicSearchStrategy from '!!raw-loader!./../../../../../src/plugins/data/public/search/i_search_strategy'; +import publicSearchStrategy from '!!raw-loader!./../../../src/plugins/data/public/search/i_search_strategy'; // @ts-ignore -import publicSearch from '!!raw-loader!./../../../../../src/plugins/data/public/search/i_search'; +import publicSearch from '!!raw-loader!./../../../src/plugins/data/public/search/i_search'; // @ts-ignore -import publicPlugin from '!!raw-loader!./../../../../../src/plugins/data/public/search/search_service'; +import publicPlugin from '!!raw-loader!./../../../src/plugins/data/public/search/search_service'; // @ts-ignore -import serverSetupContract from '!!raw-loader!./../../../../../src/plugins/data/server/search/i_search_setup'; +import serverSetupContract from '!!raw-loader!./../../../src/plugins/data/server/search/i_search_setup'; // @ts-ignore -import serverSearchStrategy from '!!raw-loader!./../../../../../src/plugins/data/server/search/i_search_strategy'; +import serverSearchStrategy from '!!raw-loader!./../../../src/plugins/data/server/search/i_search_strategy'; // @ts-ignore -import serverSearch from '!!raw-loader!./../../../../../src/plugins/data/server/search/i_search'; +import serverSearch from '!!raw-loader!./../../../src/plugins/data/server/search/i_search'; // @ts-ignore -import serverPlugin from '!!raw-loader!./../../../../../src/plugins/data/server/search/search_service'; +import serverPlugin from '!!raw-loader!./../../../src/plugins/data/server/search/search_service'; export const SearchApiPage = () => ( - str.length >= width ? str : `${str}${' '.repeat(width - str.length)}`; - -run( - async ({ log, flags }) => { - await withProcRunner(log, async proc => { - log.info('Deleting old output'); - await del(BUILD_DIR); - - const cwd = ROOT_DIR; - const env = { ...process.env }; - if (supportsColor.stdout) { - env.FORCE_COLOR = 'true'; - } - - log.info(`Starting babel and typescript${flags.watch ? ' in watch mode' : ''}`); - await Promise.all([ - ...['web', 'node'].map(subTask => - proc.run(padRight(10, `babel:${subTask}`), { - cmd: 'babel', - args: [ - 'src', - '--config-file', - require.resolve('../babel.config.js'), - '--out-dir', - resolve(BUILD_DIR, subTask), - '--extensions', - '.ts,.js,.tsx', - ...(flags.watch ? ['--watch'] : ['--quiet']), - ...(flags['source-maps'] ? ['--source-map', 'inline'] : []), - ], - wait: true, - env: { - ...env, - BABEL_ENV: subTask, - }, - cwd, - }) - ), - - proc.run(padRight(10, 'tsc'), { - cmd: 'tsc', - args: [ - '--emitDeclarationOnly', - ...(flags.watch ? ['--watch', '--preserveWatchOutput', 'true'] : []), - ...(flags['source-maps'] ? ['--declarationMap', 'true'] : []), - ], - wait: true, - env, - cwd, - }), - ]); - - log.success('Complete'); - }); - }, - { - description: 'Simple build tool for @kbn/analytics package', - flags: { - boolean: ['watch', 'source-maps'], - help: ` - --watch Run in watch mode - --source-maps Include sourcemaps - `, - }, - } -); +/* + * 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. + */ + +const { resolve } = require('path'); + +const del = require('del'); +const supportsColor = require('supports-color'); +const { run, withProcRunner } = require('@kbn/dev-utils'); + +const ROOT_DIR = resolve(__dirname, '..'); +const BUILD_DIR = resolve(ROOT_DIR, 'target'); + +const padRight = (width, str) => + str.length >= width ? str : `${str}${' '.repeat(width - str.length)}`; + +run( + async ({ log, flags }) => { + await withProcRunner(log, async proc => { + log.info('Deleting old output'); + await del(BUILD_DIR); + + const cwd = ROOT_DIR; + const env = { ...process.env }; + if (supportsColor.stdout) { + env.FORCE_COLOR = 'true'; + } + + log.info(`Starting babel and typescript${flags.watch ? ' in watch mode' : ''}`); + await Promise.all([ + ...['web', 'node'].map(subTask => + proc.run(padRight(10, `babel:${subTask}`), { + cmd: 'babel', + args: [ + 'src', + '--config-file', + require.resolve('../babel.config.js'), + '--out-dir', + resolve(BUILD_DIR, subTask), + '--extensions', + '.ts,.js,.tsx', + ...(flags.watch ? ['--watch'] : ['--quiet']), + ...(flags['source-maps'] ? ['--source-maps', 'inline'] : []), + ], + wait: true, + env: { + ...env, + BABEL_ENV: subTask, + }, + cwd, + }) + ), + + proc.run(padRight(10, 'tsc'), { + cmd: 'tsc', + args: [ + '--emitDeclarationOnly', + ...(flags.watch ? ['--watch', '--preserveWatchOutput', 'true'] : []), + ...(flags['source-maps'] ? ['--declarationMap', 'true'] : []), + ], + wait: true, + env, + cwd, + }), + ]); + + log.success('Complete'); + }); + }, + { + description: 'Simple build tool for @kbn/analytics package', + flags: { + boolean: ['watch', 'source-maps'], + help: ` + --watch Run in watch mode + --source-maps Include sourcemaps + `, + }, + } +); diff --git a/packages/kbn-i18n/scripts/build.js b/packages/kbn-i18n/scripts/build.js index f4260d31d80f..ccdddc87dbc1 100644 --- a/packages/kbn-i18n/scripts/build.js +++ b/packages/kbn-i18n/scripts/build.js @@ -55,7 +55,7 @@ run( '--extensions', '.ts,.js,.tsx', ...(flags.watch ? ['--watch'] : ['--quiet']), - ...(flags['source-maps'] ? ['--source-map', 'inline'] : []), + ...(flags['source-maps'] ? ['--source-maps', 'inline'] : []), ], wait: true, env: { diff --git a/packages/kbn-pm/dist/index.js b/packages/kbn-pm/dist/index.js index bbe12a93c241..aea85c13d7f3 100644 --- a/packages/kbn-pm/dist/index.js +++ b/packages/kbn-pm/dist/index.js @@ -23185,6 +23185,7 @@ function getProjectPaths(rootPath, options = {}) { projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'test/plugin_functional/plugins/*')); projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'test/interpreter_functional/plugins/*')); + projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'examples/*')); if (!ossOnly) { projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'x-pack')); diff --git a/packages/kbn-pm/src/config.ts b/packages/kbn-pm/src/config.ts index 4886b0c266a8..2e42a182e7ec 100644 --- a/packages/kbn-pm/src/config.ts +++ b/packages/kbn-pm/src/config.ts @@ -44,6 +44,7 @@ export function getProjectPaths(rootPath: string, options: IProjectPathOptions = // correct and the expect behavior. projectPaths.push(resolve(rootPath, 'test/plugin_functional/plugins/*')); projectPaths.push(resolve(rootPath, 'test/interpreter_functional/plugins/*')); + projectPaths.push(resolve(rootPath, 'examples/*')); if (!ossOnly) { projectPaths.push(resolve(rootPath, 'x-pack')); diff --git a/renovate.json5 b/renovate.json5 index 3886715618e9..e4cb02604f50 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -13,6 +13,7 @@ 'x-pack/package.json', 'x-pack/legacy/plugins/*/package.json', 'packages/*/package.json', + 'examples/*/package.json', 'test/plugin_functional/plugins/*/package.json', 'test/interpreter_functional/plugins/*/package.json', ], diff --git a/scripts/functional_tests.js b/scripts/functional_tests.js index b65cd3835cc0..2526e2b14e9a 100644 --- a/scripts/functional_tests.js +++ b/scripts/functional_tests.js @@ -24,4 +24,5 @@ require('@kbn/test').runTestsCli([ require.resolve('../test/plugin_functional/config.js'), require.resolve('../test/interpreter_functional/config.ts'), require.resolve('../test/ui_capabilities/newsfeed_err/config.ts'), + require.resolve('../test/examples/config.js') ]); diff --git a/src/cli/serve/serve.js b/src/cli/serve/serve.js index 48b5db318d1c..fd0502e07e42 100644 --- a/src/cli/serve/serve.js +++ b/src/cli/serve/serve.js @@ -144,6 +144,11 @@ function applyConfigOverrides(rawConfig, opts, extraCliOptions) { set('plugins.paths', _.compact([].concat( get('plugins.paths'), opts.pluginPath, + opts.runExamples ? [ + // Ideally this would automatically include all plugins in the examples dir + fromRoot('examples/demo_search'), + fromRoot('examples/search_explorer'), + ] : [], XPACK_INSTALLED && !opts.oss ? [XPACK_DIR] @@ -201,7 +206,8 @@ export default function (program) { if (!IS_KIBANA_DISTRIBUTABLE) { command - .option('--oss', 'Start Kibana without X-Pack'); + .option('--oss', 'Start Kibana without X-Pack') + .option('--run-examples', 'Adds plugin paths for all the Kibana example plugins and runs with no base path'); } if (CAN_CLUSTER) { @@ -238,7 +244,12 @@ export default function (program) { silent: !!opts.silent, watch: !!opts.watch, repl: !!opts.repl, - basePath: !!opts.basePath, + // We want to run without base path when the `--run-examples` flag is given so that we can use local + // links in other documentation sources, like "View this tutorial [here](http://localhost:5601/app/tutorial/xyz)". + // We can tell users they only have to run with `yarn start --run-examples` to get those + // local links to work. Similar to what we do for "View in Console" links in our + // elastic.co links. + basePath: opts.runExamples ? false : !!opts.basePath, optimize: !!opts.optimize, oss: !!opts.oss }, diff --git a/src/dev/register_git_hook/register_git_hook.js b/src/dev/register_git_hook/register_git_hook.js index a61922078e68..31136cab0ada 100644 --- a/src/dev/register_git_hook/register_git_hook.js +++ b/src/dev/register_git_hook/register_git_hook.js @@ -58,6 +58,15 @@ function getKbnPrecommitGitHookScript(rootPath, nodeHome, platform) { set -euo pipefail + # Make it possible to terminate pre commit hook + # using ctrl-c so nothing else would happen or be + # sent to the output. + # + # The correct exit code on that situation + # according the linux documentation project is 130 + # https://www.tldp.org/LDP/abs/html/exitcodes.html + trap "exit 130" SIGINT + has_node() { command -v node >/dev/null 2>&1 } diff --git a/src/dev/renovate/package_globs.ts b/src/dev/renovate/package_globs.ts index dd98f929be8e..825e6ffed0ec 100644 --- a/src/dev/renovate/package_globs.ts +++ b/src/dev/renovate/package_globs.ts @@ -28,6 +28,7 @@ export const PACKAGE_GLOBS = [ 'x-pack/package.json', 'x-pack/legacy/plugins/*/package.json', 'packages/*/package.json', + 'examples/*/package.json', 'test/plugin_functional/plugins/*/package.json', 'test/interpreter_functional/plugins/*/package.json', ]; diff --git a/src/dev/typescript/projects.ts b/src/dev/typescript/projects.ts index 2f8894f77ebc..602657ad7383 100644 --- a/src/dev/typescript/projects.ts +++ b/src/dev/typescript/projects.ts @@ -40,6 +40,9 @@ export const PROJECTS = [ ...glob .sync('packages/*/tsconfig.json', { cwd: REPO_ROOT }) .map(path => new Project(resolve(REPO_ROOT, path))), + ...glob + .sync('examples/*/tsconfig.json', { cwd: REPO_ROOT }) + .map(path => new Project(resolve(REPO_ROOT, path))), ...glob .sync('test/plugin_functional/plugins/*/tsconfig.json', { cwd: REPO_ROOT }) .map(path => new Project(resolve(REPO_ROOT, path))), diff --git a/src/legacy/core_plugins/kibana/public/dashboard/__tests__/__snapshots__/dashboard_empty_screen.test.tsx.snap b/src/legacy/core_plugins/kibana/public/dashboard/__tests__/__snapshots__/dashboard_empty_screen.test.tsx.snap index 07e4173d5323..8410040a0100 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/__tests__/__snapshots__/dashboard_empty_screen.test.tsx.snap +++ b/src/legacy/core_plugins/kibana/public/dashboard/__tests__/__snapshots__/dashboard_empty_screen.test.tsx.snap @@ -193,78 +193,160 @@ exports[`DashboardEmptyScreen renders correctly with visualize paragraph 1`] = ` textComponent={Symbol(react.fragment)} > - - - - - -

- This dashboard is empty. Let’s fill it up! -

-

- - Click the - - - - button in the menu bar above to add a visualization to the dashboard. - -

-

- - visit the Visualize app - , + "maxWidth": "36em", } } > - If you haven't set up any visualizations yet, - - visit the Visualize app - - to create your first visualization - -

+
+ + +
+ + + + + + +
+ + +
+

+ This dashboard is empty. Let’s fill it up! +

+
+
+ +
+ + +
+

+ Click the + + + + button in the menu bar above to add a visualization to the dashboard. +

+
+
+ +
+ + +
+

+ + visit the Visualize app + , + } + } + > + If you haven't set up any visualizations yet, + + visit the Visualize app + + to create your first visualization + +

+
+
+
+ + +
+ + +
@@ -464,51 +546,119 @@ exports[`DashboardEmptyScreen renders correctly without visualize paragraph 1`] textComponent={Symbol(react.fragment)} > - - - - - -

- This dashboard is empty. Let’s fill it up! -

-

- - Click the - - - - button in the menu bar above to start working on your new dashboard. - -

+ + +
+ + + + + + +
+ + +
+

+ This dashboard is empty. Let’s fill it up! +

+
+
+ +
+ + +
+

+ Click the + + + + button in the menu bar above to start working on your new dashboard. +

+
+
+
+ + + + +
+ diff --git a/src/legacy/core_plugins/kibana/public/dashboard/__tests__/dashboard_empty_screen.test.tsx b/src/legacy/core_plugins/kibana/public/dashboard/__tests__/dashboard_empty_screen.test.tsx index a4604d17ddec..69bdcf59bb22 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/__tests__/dashboard_empty_screen.test.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/__tests__/dashboard_empty_screen.test.tsx @@ -18,7 +18,9 @@ */ import React from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; -import { DashboardEmptyScreen, Props } from '../dashboard_empty_screen'; +import { DashboardEmptyScreen, DashboardEmptyScreenProps } from '../dashboard_empty_screen'; +// @ts-ignore +import { findTestSubject } from '@elastic/eui/lib/test'; describe('DashboardEmptyScreen', () => { const defaultProps = { @@ -26,7 +28,7 @@ describe('DashboardEmptyScreen', () => { onLinkClick: jest.fn(), }; - function mountComponent(props?: Props) { + function mountComponent(props?: DashboardEmptyScreenProps) { const compProps = props || defaultProps; const comp = mountWithIntl(); return comp; @@ -35,14 +37,14 @@ describe('DashboardEmptyScreen', () => { test('renders correctly with visualize paragraph', () => { const component = mountComponent(); expect(component).toMatchSnapshot(); - const paragraph = component.find('.linkToVisualizeParagraph'); + const paragraph = findTestSubject(component, 'linkToVisualizeParagraph'); expect(paragraph.length).toBe(1); }); test('renders correctly without visualize paragraph', () => { const component = mountComponent({ ...defaultProps, ...{ showLinkToVisualize: false } }); expect(component).toMatchSnapshot(); - const paragraph = component.find('.linkToVisualizeParagraph'); + const paragraph = findTestSubject(component, 'linkToVisualizeParagraph'); expect(paragraph.length).toBe(0); }); }); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/_dashboard_app.scss b/src/legacy/core_plugins/kibana/public/dashboard/_dashboard_app.scss index 14c35759d70a..d9eadf6c0e37 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/_dashboard_app.scss +++ b/src/legacy/core_plugins/kibana/public/dashboard/_dashboard_app.scss @@ -6,9 +6,5 @@ .dshStartScreen { text-align: center; - padding: $euiSize; - - > * { - max-width: 36em !important; - } + padding: $euiSizeS; } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/application.ts b/src/legacy/core_plugins/kibana/public/dashboard/application.ts index b58325a77e61..ef1bcab589c4 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/application.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/application.ts @@ -131,7 +131,6 @@ function createLocalAngularModule(core: AppMountContext['core'], navigation: Nav 'app/dashboard/State', 'app/dashboard/ConfirmModal', 'app/dashboard/icon', - 'app/dashboard/emptyScreen', ]); return dashboardAngularModule; } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.html b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.html index c7fd8600b73b..3cf8932958b6 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.html +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.html @@ -48,24 +48,6 @@ > -
-
-

-
- -
- -
- -
- -
-
-

{{screenTitle}}

diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx index 7eac251a532c..fd49b26e0d94 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx @@ -21,9 +21,10 @@ import _ from 'lodash'; import { i18n } from '@kbn/i18n'; import React from 'react'; import angular from 'angular'; -import { uniq } from 'lodash'; +import { uniq, noop } from 'lodash'; import { Subscription } from 'rxjs'; +import { DashboardEmptyScreen, DashboardEmptyScreenProps } from './dashboard_empty_screen'; import { subscribeWithScope, @@ -35,14 +36,11 @@ import { AppStateClass as TAppStateClass, KbnUrl, SaveOptions, - SavedObjectFinder, unhashUrl, } from './legacy_imports'; import { FilterStateManager, IndexPattern } from '../../../data/public'; import { Query, SavedQuery, IndexPatternsContract } from '../../../../../plugins/data/public'; -import './dashboard_empty_screen_directive'; - import { DashboardContainer, DASHBOARD_CONTAINER_TYPE, @@ -71,6 +69,10 @@ import { DashboardAppScope } from './dashboard_app'; import { VISUALIZE_EMBEDDABLE_TYPE } from '../visualize/embeddable'; import { convertSavedDashboardPanelToPanelState } from './lib/embeddable_saved_object_converters'; import { RenderDeps } from './application'; +import { + SavedObjectFinderProps, + SavedObjectFinderUi, +} from '../../../../../plugins/kibana_react/public'; export interface DashboardAppControllerDependencies extends RenderDeps { $scope: DashboardAppScope; @@ -115,7 +117,7 @@ export class DashboardAppController { timefilter: { timefilter }, }, }, - core: { notifications, overlays, chrome, injectedMetadata }, + core: { notifications, overlays, chrome, injectedMetadata, uiSettings, savedObjects }, }: DashboardAppControllerDependencies) { new FilterStateManager(globalState, getAppState, filterManager); const queryFilter = filterManager; @@ -143,6 +145,16 @@ export class DashboardAppController { } $scope.showSaveQuery = dashboardCapabilities.saveQuery as boolean; + $scope.getShouldShowEditHelp = () => + !dashboardStateManager.getPanels().length && + dashboardStateManager.getIsEditMode() && + !dashboardConfig.getHideWriteControls(); + + $scope.getShouldShowViewHelp = () => + !dashboardStateManager.getPanels().length && + dashboardStateManager.getIsViewMode() && + !dashboardConfig.getHideWriteControls(); + const updateIndexPatterns = (container?: DashboardContainer) => { if (!container || isErrorEmbeddable(container)) { return; @@ -171,6 +183,17 @@ export class DashboardAppController { } }; + const getEmptyScreenProps = (shouldShowEditHelp: boolean): DashboardEmptyScreenProps => { + const emptyScreenProps: DashboardEmptyScreenProps = { + onLinkClick: shouldShowEditHelp ? $scope.showAddPanel : $scope.enterEditMode, + showLinkToVisualize: shouldShowEditHelp, + }; + if (shouldShowEditHelp) { + emptyScreenProps.onVisualizeClick = noop; + } + return emptyScreenProps; + }; + const getDashboardInput = (): DashboardContainerInput => { const embeddablesMap: { [key: string]: DashboardPanelState; @@ -182,6 +205,8 @@ export class DashboardAppController { if (dashboardContainer && !isErrorEmbeddable(dashboardContainer)) { expandedPanelId = dashboardContainer.getInput().expandedPanelId; } + const shouldShowEditHelp = $scope.getShouldShowEditHelp(); + const shouldShowViewHelp = $scope.getShouldShowViewHelp(); return { id: dashboardStateManager.savedDashboard.id || '', filters: queryFilter.getFilters(), @@ -194,6 +219,7 @@ export class DashboardAppController { viewMode: dashboardStateManager.getViewMode(), panels: embeddablesMap, isFullScreenMode: dashboardStateManager.getFullScreenMode(), + isEmptyState: shouldShowEditHelp || shouldShowViewHelp, useMargins: dashboardStateManager.getUseMargins(), lastReloadRequestTime, title: dashboardStateManager.getTitle(), @@ -234,6 +260,15 @@ export class DashboardAppController { if (!isErrorEmbeddable(container)) { dashboardContainer = container; + dashboardContainer.renderEmpty = () => { + const shouldShowEditHelp = $scope.getShouldShowEditHelp(); + const shouldShowViewHelp = $scope.getShouldShowViewHelp(); + const isEmptyState = shouldShowEditHelp || shouldShowViewHelp; + return isEmptyState ? ( + + ) : null; + }; + updateIndexPatterns(dashboardContainer); outputSubscription = dashboardContainer.getOutput$().subscribe(() => { @@ -334,15 +369,6 @@ export class DashboardAppController { updateBreadcrumbs(); dashboardStateManager.registerChangeListener(updateBreadcrumbs); - $scope.getShouldShowEditHelp = () => - !dashboardStateManager.getPanels().length && - dashboardStateManager.getIsEditMode() && - !dashboardConfig.getHideWriteControls(); - $scope.getShouldShowViewHelp = () => - !dashboardStateManager.getPanels().length && - dashboardStateManager.getIsViewMode() && - !dashboardConfig.getHideWriteControls(); - const getChangesFromAppStateForContainerState = () => { const appStateDashboardInput = getDashboardInput(); if (!dashboardContainer || isErrorEmbeddable(dashboardContainer)) { @@ -718,6 +744,10 @@ export class DashboardAppController { }; navActions[TopNavIds.ADD] = () => { if (dashboardContainer && !isErrorEmbeddable(dashboardContainer)) { + const SavedObjectFinder = (props: SavedObjectFinderProps) => ( + + ); + openAddPanelFlyout({ embeddable: dashboardContainer, getAllFactories: embeddables.getEmbeddableFactories, @@ -729,6 +759,8 @@ export class DashboardAppController { } }; + navActions[TopNavIds.VISUALIZE] = async () => {}; + navActions[TopNavIds.OPTIONS] = anchorElement => { showOptionsPopover({ anchorElement, diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_empty_screen.tsx b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_empty_screen.tsx index d5a4e6e6a325..234228ba4166 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_empty_screen.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_empty_screen.tsx @@ -18,29 +18,43 @@ */ import React from 'react'; import { I18nProvider, FormattedMessage } from '@kbn/i18n/react'; -import { EuiIcon, EuiLink } from '@elastic/eui'; +import { + EuiIcon, + EuiLink, + EuiSpacer, + EuiPageContent, + EuiPageBody, + EuiPage, + EuiText, +} from '@elastic/eui'; import * as constants from './dashboard_empty_screen_constants'; -export interface Props { +export interface DashboardEmptyScreenProps { showLinkToVisualize: boolean; onLinkClick: () => void; + onVisualizeClick?: () => void; } -export function DashboardEmptyScreen({ showLinkToVisualize, onLinkClick }: Props) { +export function DashboardEmptyScreen({ + showLinkToVisualize, + onLinkClick, +}: DashboardEmptyScreenProps) { const linkToVisualizeParagraph = ( -

- - {constants.visualizeAppLinkTest} - - ), - }} - /> -

+ +

+ + {constants.visualizeAppLinkTest} + + ), + }} + /> +

+
); const paragraph = ( description1: string, @@ -50,15 +64,15 @@ export function DashboardEmptyScreen({ showLinkToVisualize, onLinkClick }: Props dataTestSubj?: string ) => { return ( -

- + +

{description1} {linkText} {description2} - -

+

+ ); }; const addVisualizationParagraph = ( @@ -70,6 +84,7 @@ export function DashboardEmptyScreen({ showLinkToVisualize, onLinkClick }: Props constants.addVisualizationLinkAriaLabel, 'emptyDashboardAddPanelButton' )} + {linkToVisualizeParagraph} ); @@ -81,11 +96,19 @@ export function DashboardEmptyScreen({ showLinkToVisualize, onLinkClick }: Props ); return ( - - -

{constants.fillDashboardTitle}

- {showLinkToVisualize ? addVisualizationParagraph : enterEditModeParagraph} -
+ + + + + + +

{constants.fillDashboardTitle}

+
+ + {showLinkToVisualize ? addVisualizationParagraph : enterEditModeParagraph} +
+
+
); } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/legacy_imports.ts b/src/legacy/core_plugins/kibana/public/dashboard/legacy_imports.ts index b0f09f0cf974..af0a833399a5 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/legacy_imports.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/legacy_imports.ts @@ -65,4 +65,3 @@ export { stateMonitorFactory, StateMonitor } from 'ui/state_management/state_mon export { ensureDefaultIndexPattern } from 'ui/legacy_compat'; export { unhashUrl } from '../../../../../plugins/kibana_utils/public'; export { IInjector } from 'ui/chrome'; -export { SavedObjectFinder } from 'ui/saved_objects/components/saved_object_finder'; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/top_nav/top_nav_ids.ts b/src/legacy/core_plugins/kibana/public/dashboard/top_nav/top_nav_ids.ts index 9df86f2ca3cc..c67d6891c18e 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/top_nav/top_nav_ids.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/top_nav/top_nav_ids.ts @@ -26,4 +26,5 @@ export const TopNavIds = { ENTER_EDIT_MODE: 'enterEditMode', CLONE: 'clone', FULL_SCREEN: 'fullScreenMode', + VISUALIZE: 'visualize', }; diff --git a/src/legacy/core_plugins/kibana/public/discover/components/top_nav/__snapshots__/open_search_panel.test.js.snap b/src/legacy/core_plugins/kibana/public/discover/components/top_nav/__snapshots__/open_search_panel.test.js.snap index cc53e4bdcdcf..2878b11040cf 100644 --- a/src/legacy/core_plugins/kibana/public/discover/components/top_nav/__snapshots__/open_search_panel.test.js.snap +++ b/src/legacy/core_plugins/kibana/public/discover/components/top_nav/__snapshots__/open_search_panel.test.js.snap @@ -26,7 +26,7 @@ exports[`render 1`] = ` - diff --git a/src/legacy/core_plugins/kibana/public/discover/components/top_nav/open_search_panel.js b/src/legacy/core_plugins/kibana/public/discover/components/top_nav/open_search_panel.js index 0c3b52fbf064..ec1763f44f25 100644 --- a/src/legacy/core_plugins/kibana/public/discover/components/top_nav/open_search_panel.js +++ b/src/legacy/core_plugins/kibana/public/discover/components/top_nav/open_search_panel.js @@ -32,11 +32,16 @@ import { EuiFlyoutBody, EuiTitle, } from '@elastic/eui'; -import { SavedObjectFinder } from 'ui/saved_objects/components/saved_object_finder'; +import { SavedObjectFinderUi } from '../../../../../../../plugins/kibana_react/public'; +import { getServices } from '../../kibana_services'; const SEARCH_OBJECT_TYPE = 'search'; export function OpenSearchPanel(props) { + const { + core: { uiSettings, savedObjects }, + } = getServices(); + return ( @@ -50,7 +55,7 @@ export function OpenSearchPanel(props) { - diff --git a/src/legacy/core_plugins/kibana/public/discover/components/top_nav/open_search_panel.test.js b/src/legacy/core_plugins/kibana/public/discover/components/top_nav/open_search_panel.test.js index ea5c0ef39604..0c82aeea9529 100644 --- a/src/legacy/core_plugins/kibana/public/discover/components/top_nav/open_search_panel.test.js +++ b/src/legacy/core_plugins/kibana/public/discover/components/top_nav/open_search_panel.test.js @@ -23,7 +23,7 @@ import { shallow } from 'enzyme'; jest.mock('../../kibana_services', () => { return { getServices: () => ({ - SavedObjectFinder: jest.fn() + core: { uiSettings: {}, savedObjects: {} }, }), }; }); diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx index 7c9efa280c9f..a377dafe9e51 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx @@ -199,7 +199,8 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory< editorParams: ['addToDashboard'], }, npStart.core.http.basePath.prepend, - npStart.core.uiSettings + npStart.core.uiSettings, + npStart.core.savedObjects ); } return undefined; diff --git a/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts b/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts index 6adcfd2cc718..b9909e522b57 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts @@ -45,7 +45,6 @@ export { PrivateProvider } from 'ui/private/private'; export { SavedObjectRegistryProvider } from 'ui/saved_objects'; export { SavedObjectSaveModal } from 'ui/saved_objects/components/saved_object_save_modal'; -export { SavedObjectFinder } from 'ui/saved_objects/components/saved_object_finder'; export { showSaveModal } from 'ui/saved_objects/show_saved_object_save_modal'; export { subscribeWithScope } from 'ui/utils/subscribe_with_scope'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.html b/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.html index 4511ac61f739..4ee8809fab22 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.html +++ b/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.html @@ -16,6 +16,7 @@ vis-types-registry="listingController.visTypeRegistry" add-base-path="listingController.addBasePath" ui-settings="listingController.uiSettings" + saved-objects="listingController.savedObjects" >
diff --git a/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js b/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js index 9b02be0581b8..b1ed5ce81d6e 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js +++ b/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js @@ -34,6 +34,7 @@ export function initListingDirective(app) { ['onClose', { watchDepth: 'reference' }], ['addBasePath', { watchDepth: 'reference' }], ['uiSettings', { watchDepth: 'reference' }], + ['savedObjects', { watchDepth: 'reference' }], 'isOpen', ]) ); @@ -54,7 +55,7 @@ export function VisualizeListingController($injector, createNewVis) { toastNotifications, uiSettings, visualizations, - core: { docLinks }, + core: { docLinks, savedObjects }, } = getServices(); const kbnUrl = $injector.get('kbnUrl'); @@ -64,6 +65,7 @@ export function VisualizeListingController($injector, createNewVis) { this.showNewVisModal = false; this.addBasePath = addBasePath; this.uiSettings = uiSettings; + this.savedObjects = savedObjects; this.createNewVis = () => { this.showNewVisModal = true; diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/__snapshots__/new_vis_modal.test.tsx.snap b/src/legacy/core_plugins/kibana/public/visualize/wizard/__snapshots__/new_vis_modal.test.tsx.snap index 5be5f5899488..04b7cddc7528 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/wizard/__snapshots__/new_vis_modal.test.tsx.snap +++ b/src/legacy/core_plugins/kibana/public/visualize/wizard/__snapshots__/new_vis_modal.test.tsx.snap @@ -108,6 +108,7 @@ exports[`NewVisModal filter for visualization types should render as expected 1` } isOpen={true} onClose={[Function]} + savedObjects={Object {}} uiSettings={ Object { "get": [MockFunction] { @@ -1413,6 +1414,7 @@ exports[`NewVisModal should render as expected 1`] = ` } isOpen={true} onClose={[Function]} + savedObjects={Object {}} uiSettings={ Object { "get": [MockFunction] { diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.test.tsx b/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.test.tsx index 0dd2091bbfee..4eafd06c7bb2 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.test.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.test.tsx @@ -29,6 +29,7 @@ jest.mock('../legacy_imports', () => ({ })); import { NewVisModal } from './new_vis_modal'; +import { SavedObjectsStart } from 'kibana/public'; describe('NewVisModal', () => { const defaultVisTypeParams = { @@ -76,6 +77,7 @@ describe('NewVisModal', () => { visTypesRegistry={visTypes} addBasePath={addBasePath} uiSettings={uiSettings} + savedObjects={{} as SavedObjectsStart} /> ); expect(wrapper).toMatchSnapshot(); @@ -89,6 +91,7 @@ describe('NewVisModal', () => { visTypesRegistry={visTypes} addBasePath={addBasePath} uiSettings={uiSettings} + savedObjects={{} as SavedObjectsStart} /> ); expect(wrapper.find('[data-test-subj="visType-vis"]').exists()).toBe(true); @@ -104,6 +107,7 @@ describe('NewVisModal', () => { visTypesRegistry={visTypes} addBasePath={addBasePath} uiSettings={uiSettings} + savedObjects={{} as SavedObjectsStart} /> ); const visButton = wrapper.find('button[data-test-subj="visType-vis"]'); @@ -121,6 +125,7 @@ describe('NewVisModal', () => { editorParams={['foo=true', 'bar=42']} addBasePath={addBasePath} uiSettings={uiSettings} + savedObjects={{} as SavedObjectsStart} /> ); const visButton = wrapper.find('button[data-test-subj="visType-vis"]'); @@ -138,6 +143,7 @@ describe('NewVisModal', () => { visTypesRegistry={visTypes} addBasePath={addBasePath} uiSettings={uiSettings} + savedObjects={{} as SavedObjectsStart} /> ); const searchBox = wrapper.find('input[data-test-subj="filterVisType"]'); @@ -156,6 +162,7 @@ describe('NewVisModal', () => { visTypesRegistry={visTypes} addBasePath={addBasePath} uiSettings={uiSettings} + savedObjects={{} as SavedObjectsStart} /> ); expect(wrapper.find('[data-test-subj="visType-visExp"]').exists()).toBe(false); @@ -170,6 +177,7 @@ describe('NewVisModal', () => { visTypesRegistry={visTypes} addBasePath={addBasePath} uiSettings={uiSettings} + savedObjects={{} as SavedObjectsStart} /> ); expect(wrapper.find('[data-test-subj="visType-visExp"]').exists()).toBe(true); diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.tsx b/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.tsx index 0b46b562f214..0402265610fb 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.tsx @@ -22,7 +22,7 @@ import React from 'react'; import { EuiModal, EuiOverlayMask } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { IUiSettingsClient } from 'kibana/public'; +import { IUiSettingsClient, SavedObjectsStart } from 'kibana/public'; import { VisType } from '../legacy_imports'; import { VisualizeConstants } from '../visualize_constants'; import { createUiStatsReporter, METRIC_TYPE } from '../../../../ui_metric/public'; @@ -37,6 +37,7 @@ interface TypeSelectionProps { editorParams?: string[]; addBasePath: (path: string) => string; uiSettings: IUiSettingsClient; + savedObjects: SavedObjectsStart; } interface TypeSelectionState { @@ -81,7 +82,12 @@ class NewVisModal extends React.Component - + ) : ( void; visType: VisType; + uiSettings: IUiSettingsClient; + savedObjects: SavedObjectsStart; } export class SearchSelection extends React.Component { @@ -50,7 +54,7 @@ export class SearchSelection extends React.Component { - { }, ]} fixedPageSize={this.fixedPageSize} + uiSettings={this.props.uiSettings} + savedObjects={this.props.savedObjects} /> diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/show_new_vis.tsx b/src/legacy/core_plugins/kibana/public/visualize/wizard/show_new_vis.tsx index 92320f7bb443..88838e16c40e 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/wizard/show_new_vis.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/wizard/show_new_vis.tsx @@ -21,7 +21,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { I18nProvider } from '@kbn/i18n/react'; -import { IUiSettingsClient } from 'kibana/public'; +import { IUiSettingsClient, SavedObjectsStart } from 'kibana/public'; import { NewVisModal } from './new_vis_modal'; import { TypesStart } from '../../../../visualizations/public/np_ready/public/types'; @@ -33,7 +33,8 @@ export function showNewVisModal( visTypeRegistry: TypesStart, { editorParams = [] }: ShowNewVisModalParams = {}, addBasePath: (path: string) => string, - uiSettings: IUiSettingsClient + uiSettings: IUiSettingsClient, + savedObjects: SavedObjectsStart ) { const container = document.createElement('div'); const onClose = () => { @@ -51,6 +52,7 @@ export function showNewVisModal( editorParams={editorParams} addBasePath={addBasePath} uiSettings={uiSettings} + savedObjects={savedObjects} /> ); diff --git a/src/legacy/core_plugins/kibana/server/tutorials/apm/index_pattern.json b/src/legacy/core_plugins/kibana/server/tutorials/apm/index_pattern.json index 69a165c09c2f..9001613623cc 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/apm/index_pattern.json +++ b/src/legacy/core_plugins/kibana/server/tutorials/apm/index_pattern.json @@ -1,7 +1,7 @@ { "attributes": { "fieldFormatMap": "{\"client.bytes\":{\"id\":\"bytes\"},\"client.nat.port\":{\"id\":\"string\"},\"client.port\":{\"id\":\"string\"},\"destination.bytes\":{\"id\":\"bytes\"},\"destination.nat.port\":{\"id\":\"string\"},\"destination.port\":{\"id\":\"string\"},\"event.duration\":{\"id\":\"duration\",\"params\":{\"inputFormat\":\"nanoseconds\",\"outputFormat\":\"asMilliseconds\",\"outputPrecision\":1}},\"event.sequence\":{\"id\":\"string\"},\"event.severity\":{\"id\":\"string\"},\"http.request.body.bytes\":{\"id\":\"bytes\"},\"http.request.bytes\":{\"id\":\"bytes\"},\"http.response.body.bytes\":{\"id\":\"bytes\"},\"http.response.bytes\":{\"id\":\"bytes\"},\"http.response.status_code\":{\"id\":\"string\"},\"log.syslog.facility.code\":{\"id\":\"string\"},\"log.syslog.priority\":{\"id\":\"string\"},\"network.bytes\":{\"id\":\"bytes\"},\"package.size\":{\"id\":\"string\"},\"process.pgid\":{\"id\":\"string\"},\"process.pid\":{\"id\":\"string\"},\"process.ppid\":{\"id\":\"string\"},\"process.thread.id\":{\"id\":\"string\"},\"server.bytes\":{\"id\":\"bytes\"},\"server.nat.port\":{\"id\":\"string\"},\"server.port\":{\"id\":\"string\"},\"source.bytes\":{\"id\":\"bytes\"},\"source.nat.port\":{\"id\":\"string\"},\"source.port\":{\"id\":\"string\"},\"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\"},\"url.port\":{\"id\":\"string\"},\"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\":\"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\":\"tags\",\"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\":\"agent.id\",\"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.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"as.organization.name\",\"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.as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.as.organization.name\",\"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.domain\",\"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.continent_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.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"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.name\",\"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.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"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.nat.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.nat.port\",\"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.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.name\",\"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\":\"cloud.availability_zone\",\"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.provider\",\"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\":\"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.labels\",\"scripted\":false,\"searchable\":true},{\"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.runtime\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"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.as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.as.organization.name\",\"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.domain\",\"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.continent_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.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"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.name\",\"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.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"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.nat.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.nat.port\",\"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.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.class\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.data\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.ttl\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.header_flags\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.op_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.class\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.subdomain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.resolved_ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.response_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.type\",\"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\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.code\",\"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.stack_trace\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"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\":\"event.action\",\"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.code\",\"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.dataset\",\"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.end\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"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.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.module\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.original\",\"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.provider\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"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\":\"event.sequence\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"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\":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.timezone\",\"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\":\"file.accessed\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.created\",\"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\":\"file.device\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.directory\",\"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.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.hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.hash.sha512\",\"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.mode\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"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.name\",\"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.path\",\"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.target_path\",\"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.uid\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"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\":\"hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"hash.sha512\",\"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.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"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_iso_code\",\"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.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"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\":\"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.region_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.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,\"type\":\"ip\"},{\"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.name\",\"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.full\",\"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.os.name\",\"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.version\",\"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.uptime\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"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.request.body.content\",\"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.method\",\"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.body.bytes\",\"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.response.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"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.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"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\":true,\"indexed\":true,\"name\":\"log.logger\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.origin.file.line\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.origin.file.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.origin.function\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.facility.code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.facility.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.priority\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.severity.code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.severity.name\",\"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.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"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.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,\"type\":\"ip\"},{\"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.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"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\":\"network.protocol\",\"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.type\",\"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.continent_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.country_name\",\"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.name\",\"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.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"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.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"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.name\",\"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.full\",\"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.os.name\",\"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.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.product\",\"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.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\":\"organization.id\",\"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\":\"os.family\",\"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.kernel\",\"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.platform\",\"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\":\"package.architecture\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.checksum\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.description\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.install_scope\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.installed\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.license\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"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.hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.hash.sha512\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"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.pgid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"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.ppid\",\"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.thread.id\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.thread.name\",\"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.uptime\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"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,\"type\":\"ip\"},{\"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.as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.as.organization.name\",\"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.domain\",\"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.continent_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.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"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.name\",\"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.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"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.nat.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.nat.port\",\"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.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.name\",\"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\":\"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.node.name\",\"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.type\",\"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\":\"source.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.as.organization.name\",\"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.domain\",\"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.continent_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.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"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.name\",\"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.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"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.nat.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.nat.port\",\"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.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.framework\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.tactic.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.tactic.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.tactic.reference\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.reference\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tracing.trace.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tracing.transaction.id\",\"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.extension\",\"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.full\",\"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.password\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"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.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"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.registered_domain\",\"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.top_level_domain\",\"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\":\"user.domain\",\"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.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.group.domain\",\"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.hash\",\"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_agent.device.name\",\"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.original\",\"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.full\",\"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\":\"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.platform\",\"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.version\",\"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\":\"fields\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"timeseries.instance\",\"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\":\"cloud.image.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\":\"host.containerized\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.build\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.codename\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"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.replicaset.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.deployment.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.statefulset.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"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\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"enabled\":false,\"indexed\":false,\"name\":\"http.request.headers\",\"scripted\":false,\"searchable\":false},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.finished\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"enabled\":false,\"indexed\":false,\"name\":\"http.response.headers\",\"scripted\":false,\"searchable\":false},{\"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,\"type\":\"boolean\"},{\"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\":\"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.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.duration.sum.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.self_time.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.self_time.sum.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.breakdown.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"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\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.self_time.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.self_time.sum.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"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\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"experimental\",\"scripted\":false,\"searchable\":true},{\"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,\"type\":\"boolean\"},{\"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.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,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.db.link\",\"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\":\"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\":\"tags\",\"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\":\"agent.id\",\"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.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"as.organization.name\",\"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.as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.as.organization.name\",\"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.domain\",\"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.continent_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.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"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.name\",\"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.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"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.nat.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.nat.port\",\"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.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.name\",\"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\":\"cloud.availability_zone\",\"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.provider\",\"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\":\"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.labels\",\"scripted\":false,\"searchable\":true},{\"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.runtime\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"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.as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.as.organization.name\",\"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.domain\",\"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.continent_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.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"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.name\",\"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.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"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.nat.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.nat.port\",\"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.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.class\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.data\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.ttl\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.header_flags\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.op_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.class\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.subdomain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.resolved_ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.response_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.type\",\"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\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.code\",\"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.stack_trace\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"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\":\"event.action\",\"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.code\",\"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.dataset\",\"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.end\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"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.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.module\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.original\",\"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.provider\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"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\":\"event.sequence\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"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\":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.timezone\",\"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\":\"file.accessed\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.created\",\"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\":\"file.device\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.directory\",\"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.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.hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.hash.sha512\",\"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.mode\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"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.name\",\"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.path\",\"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.target_path\",\"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.uid\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"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\":\"hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"hash.sha512\",\"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.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"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_iso_code\",\"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.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"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\":\"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.region_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.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,\"type\":\"ip\"},{\"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.name\",\"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.full\",\"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.os.name\",\"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.version\",\"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.uptime\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"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.request.body.content\",\"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.method\",\"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.body.bytes\",\"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.response.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"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.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"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\":true,\"indexed\":true,\"name\":\"log.logger\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.origin.file.line\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.origin.file.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.origin.function\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.facility.code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.facility.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.priority\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.severity.code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.severity.name\",\"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.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"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.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,\"type\":\"ip\"},{\"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.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"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\":\"network.protocol\",\"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.type\",\"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.continent_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.country_name\",\"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.name\",\"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.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"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.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"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.name\",\"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.full\",\"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.os.name\",\"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.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.product\",\"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.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\":\"organization.id\",\"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\":\"os.family\",\"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.kernel\",\"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.platform\",\"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\":\"package.architecture\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.checksum\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.description\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.install_scope\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.installed\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.license\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"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.hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.hash.sha512\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"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.pgid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"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.ppid\",\"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.thread.id\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.thread.name\",\"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.uptime\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"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,\"type\":\"ip\"},{\"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.as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.as.organization.name\",\"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.domain\",\"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.continent_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.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"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.name\",\"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.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"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.nat.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.nat.port\",\"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.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.name\",\"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\":\"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.node.name\",\"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.type\",\"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\":\"source.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.as.organization.name\",\"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.domain\",\"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.continent_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.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"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.name\",\"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.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"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.nat.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.nat.port\",\"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.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.framework\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.tactic.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.tactic.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.tactic.reference\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.reference\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tracing.trace.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tracing.transaction.id\",\"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.extension\",\"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.full\",\"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.password\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"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.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"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.registered_domain\",\"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.top_level_domain\",\"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\":\"user.domain\",\"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.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.group.domain\",\"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.hash\",\"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_agent.device.name\",\"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.original\",\"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.full\",\"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\":\"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.platform\",\"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.version\",\"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\":\"fields\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"timeseries.instance\",\"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\":\"cloud.image.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\":\"host.containerized\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.build\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.codename\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"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.replicaset.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.deployment.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.statefulset.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"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\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"enabled\":false,\"indexed\":false,\"name\":\"http.request.headers\",\"scripted\":false,\"searchable\":false},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.finished\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"enabled\":false,\"indexed\":false,\"name\":\"http.response.headers\",\"scripted\":false,\"searchable\":false},{\"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,\"type\":\"boolean\"},{\"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\":\"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.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.duration.sum.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.self_time.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.self_time.sum.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.breakdown.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"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\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.self_time.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.self_time.sum.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"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\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"experimental\",\"scripted\":false,\"searchable\":true},{\"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,\"type\":\"boolean\"},{\"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\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.cpu.ns\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.samples.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.alloc_objects.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.alloc_space.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.inuse_objects.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.inuse_space.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.duration\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.top.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.top.function\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.top.filename\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.top.line\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.stack.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.stack.function\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.stack.filename\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.stack.line\",\"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.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,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.db.link\",\"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/telemetry/public/components/__snapshots__/telemetry_form.test.js.snap b/src/legacy/core_plugins/telemetry/public/components/__snapshots__/telemetry_form.test.js.snap index a7f8d72e016f..079a43e77616 100644 --- a/src/legacy/core_plugins/telemetry/public/components/__snapshots__/telemetry_form.test.js.snap +++ b/src/legacy/core_plugins/telemetry/public/components/__snapshots__/telemetry_form.test.js.snap @@ -38,7 +38,24 @@ exports[`TelemetryForm renders as expected when allows to change optIn status 1` "defVal": true, "description":

- Help us improve the Elastic Stack by providing usage statistics for basic features. We will not share this data outside of Elastic. + + + , + } + } + />

-

- - - -

, "type": "boolean", "value": false, diff --git a/src/legacy/core_plugins/telemetry/public/components/telemetry_form.js b/src/legacy/core_plugins/telemetry/public/components/telemetry_form.js index d4bbe1029b40..f6012a271cde 100644 --- a/src/legacy/core_plugins/telemetry/public/components/telemetry_form.js +++ b/src/legacy/core_plugins/telemetry/public/components/telemetry_form.js @@ -29,7 +29,7 @@ import { EuiSpacer, EuiText, } from '@elastic/eui'; -import { getConfigTelemetryDesc, PRIVACY_STATEMENT_URL } from '../../common/constants'; +import { PRIVACY_STATEMENT_URL } from '../../common/constants'; import { OptInExampleFlyout } from './opt_in_details_component'; import { Field } from 'ui/management'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -162,7 +162,23 @@ export class TelemetryForm extends Component { renderDescription = () => ( -

{getConfigTelemetryDesc()}

+

+ + + + ) + }} + /> +

-

- - - -

) diff --git a/src/legacy/server/url_shortening/routes/create_routes.js b/src/legacy/server/url_shortening/routes/create_routes.js index 091eabcf47c1..c6347ace873f 100644 --- a/src/legacy/server/url_shortening/routes/create_routes.js +++ b/src/legacy/server/url_shortening/routes/create_routes.js @@ -19,12 +19,10 @@ import { shortUrlLookupProvider } from './lib/short_url_lookup'; import { createGotoRoute } from './goto'; -import { createShortenUrlRoute } from './shorten_url'; export function createRoutes(server) { const shortUrlLookup = shortUrlLookupProvider(server); server.route(createGotoRoute({ server, shortUrlLookup })); - server.route(createShortenUrlRoute({ shortUrlLookup })); } diff --git a/src/legacy/server/url_shortening/routes/goto.js b/src/legacy/server/url_shortening/routes/goto.js index 675bc5df5067..60a34499dd2d 100644 --- a/src/legacy/server/url_shortening/routes/goto.js +++ b/src/legacy/server/url_shortening/routes/goto.js @@ -22,18 +22,12 @@ import { shortUrlAssertValid } from './lib/short_url_assert_valid'; export const createGotoRoute = ({ server, shortUrlLookup }) => ({ method: 'GET', - path: '/goto/{urlId}', + path: '/goto_LP/{urlId}', handler: async function (request, h) { try { const url = await shortUrlLookup.getUrl(request.params.urlId, request); shortUrlAssertValid(url); - const uiSettings = request.getUiSettingsService(); - const stateStoreInSessionStorage = await uiSettings.get('state:storeInSessionStorage'); - if (!stateStoreInSessionStorage) { - return h.redirect(request.getBasePath() + url); - } - const app = server.getHiddenUiAppById('stateSessionStorageRedirect'); return h.renderApp(app, { redirectUrl: url, diff --git a/src/legacy/server/url_shortening/routes/lib/short_url_lookup.js b/src/legacy/server/url_shortening/routes/lib/short_url_lookup.js index c4f6af03d7d9..3a4b96c802c5 100644 --- a/src/legacy/server/url_shortening/routes/lib/short_url_lookup.js +++ b/src/legacy/server/url_shortening/routes/lib/short_url_lookup.js @@ -17,7 +17,6 @@ * under the License. */ -import crypto from 'crypto'; import { get } from 'lodash'; export function shortUrlLookupProvider(server) { @@ -34,29 +33,6 @@ export function shortUrlLookupProvider(server) { } return { - async generateUrlId(url, req) { - const id = crypto.createHash('md5').update(url).digest('hex'); - const savedObjectsClient = req.getSavedObjectsClient(); - const { isConflictError } = savedObjectsClient.errors; - - try { - const doc = await savedObjectsClient.create('url', { - url, - accessCount: 0, - createDate: new Date(), - accessDate: new Date() - }, { id }); - - return doc.id; - } catch (error) { - if (isConflictError(error)) { - return id; - } - - throw error; - } - }, - async getUrl(id, req) { const doc = await req.getSavedObjectsClient().get('url', id); updateMetadata(doc, req); diff --git a/src/legacy/server/url_shortening/routes/lib/short_url_lookup.test.js b/src/legacy/server/url_shortening/routes/lib/short_url_lookup.test.js index 033aeb92926a..7303682c63e0 100644 --- a/src/legacy/server/url_shortening/routes/lib/short_url_lookup.test.js +++ b/src/legacy/server/url_shortening/routes/lib/short_url_lookup.test.js @@ -48,43 +48,6 @@ describe('shortUrlLookupProvider', () => { sandbox.restore(); }); - describe('generateUrlId', () => { - it('returns the document id', async () => { - const id = await shortUrl.generateUrlId(URL, req); - expect(id).toEqual(ID); - }); - - it('provides correct arguments to savedObjectsClient', async () => { - await shortUrl.generateUrlId(URL, req); - - sinon.assert.calledOnce(savedObjectsClient.create); - const [type, attributes, options] = savedObjectsClient.create.getCall(0).args; - - expect(type).toEqual(TYPE); - expect(Object.keys(attributes).sort()).toEqual(['accessCount', 'accessDate', 'createDate', 'url']); - expect(attributes.url).toEqual(URL); - expect(options.id).toEqual(ID); - }); - - it('passes persists attributes', async () => { - await shortUrl.generateUrlId(URL, req); - - sinon.assert.calledOnce(savedObjectsClient.create); - const [type, attributes] = savedObjectsClient.create.getCall(0).args; - - expect(type).toEqual(TYPE); - expect(Object.keys(attributes).sort()).toEqual(['accessCount', 'accessDate', 'createDate', 'url']); - expect(attributes.url).toEqual(URL); - }); - - it('gracefully handles version conflict', async () => { - const error = savedObjectsClient.errors.decorateConflictError(new Error()); - savedObjectsClient.create.throws(error); - const id = await shortUrl.generateUrlId(URL, req); - expect(id).toEqual(ID); - }); - }); - describe('getUrl', () => { beforeEach(() => { const attributes = { accessCount: 2, url: URL }; diff --git a/src/legacy/ui/public/agg_types/buckets/geo_hash.ts b/src/legacy/ui/public/agg_types/buckets/geo_hash.ts index 700f5a048fce..0acbaf4aa02a 100644 --- a/src/legacy/ui/public/agg_types/buckets/geo_hash.ts +++ b/src/legacy/ui/public/agg_types/buckets/geo_hash.ts @@ -18,13 +18,13 @@ */ import { i18n } from '@kbn/i18n'; +import { geohashColumns } from 'ui/vis/map/decode_geo_hash'; import chrome from '../../chrome'; import { BucketAggType, IBucketAggConfig } from './_bucket_agg_type'; import { AutoPrecisionParamEditor } from '../../vis/editors/default/controls/auto_precision'; import { UseGeocentroidParamEditor } from '../../vis/editors/default/controls/use_geocentroid'; import { IsFilteredByCollarParamEditor } from '../../vis/editors/default/controls/is_filtered_by_collar'; import { PrecisionParamEditor } from '../../vis/editors/default/controls/precision'; -import { geohashColumns } from '../../utils/decode_geo_hash'; import { AggGroupNames } from '../../vis/editors/default/agg_groups'; import { KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; diff --git a/src/legacy/ui/public/saved_objects/components/saved_object_finder.tsx b/src/legacy/ui/public/saved_objects/components/saved_object_finder.tsx deleted file mode 100644 index 5b787eb26550..000000000000 --- a/src/legacy/ui/public/saved_objects/components/saved_object_finder.tsx +++ /dev/null @@ -1,108 +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 React from 'react'; -import { npStart } from 'ui/new_platform'; -import { IconType } from '@elastic/eui'; -import { SavedObjectAttributes } from 'src/core/server'; -import { SimpleSavedObject } from 'src/core/public'; -import { SavedObjectFinder as SavedObjectFinderNP } from '../../../../../plugins/kibana_react/public'; - -/** - * DO NOT USE THIS COMPONENT, IT IS DEPRECATED. - * Use the one in `src/plugins/kibana_react` instead. - */ - -export interface SavedObjectMetaData { - type: string; - name: string; - getIconForSavedObject(savedObject: SimpleSavedObject): IconType; - getTooltipForSavedObject?(savedObject: SimpleSavedObject): string; - showSavedObject?(savedObject: SimpleSavedObject): boolean; -} - -interface BaseSavedObjectFinder { - /** - * @deprecated - * - * Use component in `src/plugins/kibana_react` instead. - */ - onChoose?: ( - id: SimpleSavedObject['id'], - type: SimpleSavedObject['type'], - name: string - ) => void; - /** - * @deprecated - * - * Use component in `src/plugins/kibana_react` instead. - */ - noItemsMessage?: React.ReactNode; - /** - * @deprecated - * - * Use component in `src/plugins/kibana_react` instead. - */ - savedObjectMetaData: Array>; - /** - * @deprecated - * - * Use component in `src/plugins/kibana_react` instead. - */ - showFilter?: boolean; -} - -interface SavedObjectFinderFixedPage extends BaseSavedObjectFinder { - /** - * @deprecated - * - * Use component in `src/plugins/kibana_react` instead. - */ - initialPageSize?: undefined; - /** - * @deprecated - * - * Use component in `src/plugins/kibana_react` instead. - */ - fixedPageSize: number; -} - -interface SavedObjectFinderInitialPageSize extends BaseSavedObjectFinder { - /** - * @deprecated - * - * Use component in `src/plugins/kibana_react` instead. - */ - initialPageSize?: 5 | 10 | 15 | 25; - /** - * @deprecated - * - * Use component in `src/plugins/kibana_react` instead. - */ - fixedPageSize?: undefined; -} -type SavedObjectFinderProps = SavedObjectFinderFixedPage | SavedObjectFinderInitialPageSize; - -export const SavedObjectFinder: React.FC = props => ( - -); diff --git a/src/legacy/ui/public/state_management/state.js b/src/legacy/ui/public/state_management/state.js index 27186b424997..e868abb98c85 100644 --- a/src/legacy/ui/public/state_management/state.js +++ b/src/legacy/ui/public/state_management/state.js @@ -316,6 +316,27 @@ export function StateProvider(Private, $rootScope, $location, stateManagementCon return this._urlParam; }; + /** + * Returns an object with each property name and value corresponding to the entries in this collection + * excluding fields started from '$', '_' and all methods + * + * @return {object} + */ + State.prototype.toObject = function () { + return _.omit(this, (value, key) => { + return key.charAt(0) === '$' || key.charAt(0) === '_' || _.isFunction(value); + }); + }; + + /** Alias for method 'toObject' + * + * @obsolete Please use 'toObject' method instead + * @return {object} + */ + State.prototype.toJSON = function () { + return this.toObject(); + }; + return State; } diff --git a/src/legacy/ui/public/utils/range.d.ts b/src/legacy/ui/public/time_buckets/index.d.ts similarity index 81% rename from src/legacy/ui/public/utils/range.d.ts rename to src/legacy/ui/public/time_buckets/index.d.ts index c484c6f43eeb..70b9495b81f0 100644 --- a/src/legacy/ui/public/utils/range.d.ts +++ b/src/legacy/ui/public/time_buckets/index.d.ts @@ -17,12 +17,6 @@ * under the License. */ -export function parseRange(input: string): Range; - -export interface Range { - min: number; - max: number; - minInclusive: boolean; - maxInclusive: boolean; - within(n: number): boolean; +declare module 'ui/time_buckets' { + export const TimeBuckets: any; } diff --git a/src/legacy/ui/public/utils/__tests__/base_object.js b/src/legacy/ui/public/utils/__tests__/base_object.js deleted file mode 100644 index dfc5688c7b2f..000000000000 --- a/src/legacy/ui/public/utils/__tests__/base_object.js +++ /dev/null @@ -1,57 +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 expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import '../../private'; - -import { BaseObject } from '../base_object'; - -describe('Base Object', function () { - beforeEach(ngMock.module('kibana')); - - it('should take an inital set of values', function () { - const baseObject = new BaseObject({ message: 'test' }); - expect(baseObject).to.have.property('message', 'test'); - }); - - it('should serialize attributes to RISON', function () { - const baseObject = new BaseObject(); - baseObject.message = 'Testing... 1234'; - const rison = baseObject.toRISON(); - expect(rison).to.equal('(message:\'Testing... 1234\')'); - }); - - it('should not serialize $$attributes to RISON', function () { - const baseObject = new BaseObject(); - baseObject.$$attributes = { foo: 'bar' }; - baseObject.message = 'Testing... 1234'; - const rison = baseObject.toRISON(); - expect(rison).to.equal('(message:\'Testing... 1234\')'); - }); - - it('should serialize attributes for JSON', function () { - const baseObject = new BaseObject(); - baseObject.message = 'Testing... 1234'; - baseObject._private = 'foo'; - baseObject.$private = 'stuff'; - const json = JSON.stringify(baseObject); - expect(json).to.equal('{"message":"Testing... 1234"}'); - }); -}); diff --git a/src/legacy/ui/public/utils/__tests__/simple_emitter.js b/src/legacy/ui/public/utils/__tests__/simple_emitter.js deleted file mode 100644 index 25224a409f8f..000000000000 --- a/src/legacy/ui/public/utils/__tests__/simple_emitter.js +++ /dev/null @@ -1,175 +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 { SimpleEmitter } from '../simple_emitter'; -import expect from '@kbn/expect'; -import sinon from 'sinon'; - -describe('SimpleEmitter class', function () { - let emitter; - - beforeEach(function () { - emitter = new SimpleEmitter(); - }); - - it('constructs an event emitter', function () { - expect(emitter).to.have.property('on'); - expect(emitter).to.have.property('off'); - expect(emitter).to.have.property('emit'); - expect(emitter).to.have.property('listenerCount'); - expect(emitter).to.have.property('removeAllListeners'); - }); - - describe('#listenerCount', function () { - it('counts all event listeners without any arg', function () { - expect(emitter.listenerCount()).to.be(0); - emitter.on('a', function () {}); - expect(emitter.listenerCount()).to.be(1); - emitter.on('b', function () {}); - expect(emitter.listenerCount()).to.be(2); - }); - - it('limits to the event that is passed in', function () { - expect(emitter.listenerCount()).to.be(0); - emitter.on('a', function () {}); - expect(emitter.listenerCount('a')).to.be(1); - emitter.on('a', function () {}); - expect(emitter.listenerCount('a')).to.be(2); - emitter.on('b', function () {}); - expect(emitter.listenerCount('a')).to.be(2); - expect(emitter.listenerCount('b')).to.be(1); - expect(emitter.listenerCount()).to.be(3); - }); - }); - - describe('#on', function () { - it('registers a handler', function () { - const handler = sinon.stub(); - emitter.on('a', handler); - expect(emitter.listenerCount('a')).to.be(1); - - expect(handler.callCount).to.be(0); - emitter.emit('a'); - expect(handler.callCount).to.be(1); - }); - - it('allows multiple event handlers for the same event', function () { - emitter.on('a', function () {}); - emitter.on('a', function () {}); - expect(emitter.listenerCount('a')).to.be(2); - }); - - it('allows the same function to be registered multiple times', function () { - const handler = function () {}; - emitter.on('a', handler); - expect(emitter.listenerCount()).to.be(1); - emitter.on('a', handler); - expect(emitter.listenerCount()).to.be(2); - }); - }); - - describe('#off', function () { - it('removes a listener if it was registered', function () { - const handler = sinon.stub(); - expect(emitter.listenerCount()).to.be(0); - emitter.on('a', handler); - expect(emitter.listenerCount('a')).to.be(1); - emitter.off('a', handler); - expect(emitter.listenerCount('a')).to.be(0); - }); - - it('clears all listeners if no handler is passed', function () { - emitter.on('a', function () {}); - emitter.on('a', function () {}); - expect(emitter.listenerCount()).to.be(2); - emitter.off('a'); - expect(emitter.listenerCount()).to.be(0); - }); - - it('does not mind if the listener is not registered', function () { - emitter.off('a', function () {}); - }); - - it('does not mind if the event has no listeners', function () { - emitter.off('a'); - }); - }); - - describe('#emit', function () { - it('calls the handlers in the order they were defined', function () { - let i = 0; - const incr = function () { return ++i; }; - const one = sinon.spy(incr); - const two = sinon.spy(incr); - const three = sinon.spy(incr); - const four = sinon.spy(incr); - - emitter - .on('a', one) - .on('a', two) - .on('a', three) - .on('a', four) - .emit('a'); - - expect(one).to.have.property('callCount', 1); - expect(one.returned(1)).to.be.ok(); - - expect(two).to.have.property('callCount', 1); - expect(two.returned(2)).to.be.ok(); - - expect(three).to.have.property('callCount', 1); - expect(three.returned(3)).to.be.ok(); - - expect(four).to.have.property('callCount', 1); - expect(four.returned(4)).to.be.ok(); - }); - - it('always emits the handlers that were initially registered', function () { - - const destructive = sinon.spy(function () { - emitter.removeAllListeners(); - expect(emitter.listenerCount()).to.be(0); - }); - const stub = sinon.stub(); - - emitter.on('run', destructive).on('run', stub).emit('run'); - - expect(destructive).to.have.property('callCount', 1); - expect(stub).to.have.property('callCount', 1); - }); - - it('applies all arguments except the first', function () { - emitter - .on('a', function (a, b, c) { - expect(a).to.be('foo'); - expect(b).to.be('bar'); - expect(c).to.be('baz'); - }) - .emit('a', 'foo', 'bar', 'baz'); - }); - - it('uses the SimpleEmitter as the this context', function () { - emitter - .on('a', function () { - expect(this).to.be(emitter); - }) - .emit('a'); - }); - }); -}); diff --git a/src/legacy/ui/public/utils/base_object.ts b/src/legacy/ui/public/utils/base_object.ts deleted file mode 100644 index 63c7ebf6de5b..000000000000 --- a/src/legacy/ui/public/utils/base_object.ts +++ /dev/null @@ -1,47 +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 angular from 'angular'; -import _ from 'lodash'; -// @ts-ignore -- awaiting https://github.com/w33ble/rison-node/issues/1 -import rison from 'rison-node'; - -export class BaseObject { - // Set the attributes or default to an empty object - constructor(attributes: Record = {}) { - // Set the attributes or default to an empty object - _.assign(this, attributes); - } - - public toObject() { - // return just the data. - return _.omit(this, (value: any, key: string) => { - return key.charAt(0) === '$' || key.charAt(0) === '_' || _.isFunction(value); - }); - } - - public toRISON() { - // Use Angular to remove the private vars, and JSON.stringify to serialize - return rison.encode(JSON.parse(angular.toJson(this))); - } - - public toJSON() { - return this.toObject(); - } -} diff --git a/src/legacy/ui/public/utils/simple_emitter.js b/src/legacy/ui/public/utils/simple_emitter.js index 84397962c286..503798ba160d 100644 --- a/src/legacy/ui/public/utils/simple_emitter.js +++ b/src/legacy/ui/public/utils/simple_emitter.js @@ -18,8 +18,6 @@ */ import _ from 'lodash'; -import { BaseObject } from './base_object'; -import { createLegacyClass } from './legacy_class'; /** * Simple event emitter class used in the vislib. Calls @@ -27,7 +25,6 @@ import { createLegacyClass } from './legacy_class'; * * @class */ -createLegacyClass(SimpleEmitter).inherits(BaseObject); export function SimpleEmitter() { this._listeners = {}; } @@ -134,4 +131,3 @@ SimpleEmitter.prototype.listenerCount = function (name) { return count + _.size(handlers); }, 0); }; - diff --git a/src/legacy/ui/public/utils/simple_emitter.test.js b/src/legacy/ui/public/utils/simple_emitter.test.js new file mode 100644 index 000000000000..ff884a12be7e --- /dev/null +++ b/src/legacy/ui/public/utils/simple_emitter.test.js @@ -0,0 +1,173 @@ +/* + * 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 { SimpleEmitter } from './simple_emitter'; +import sinon from 'sinon'; + +describe('SimpleEmitter class', () => { + let emitter; + + beforeEach(() => { + emitter = new SimpleEmitter(); + }); + + it('constructs an event emitter', () => { + expect(emitter).toHaveProperty('on'); + expect(emitter).toHaveProperty('off'); + expect(emitter).toHaveProperty('emit'); + expect(emitter).toHaveProperty('listenerCount'); + expect(emitter).toHaveProperty('removeAllListeners'); + }); + + describe('#listenerCount', () => { + it('counts all event listeners without any arg', () => { + expect(emitter.listenerCount()).toBe(0); + emitter.on('a', () => {}); + expect(emitter.listenerCount()).toBe(1); + emitter.on('b', () => {}); + expect(emitter.listenerCount()).toBe(2); + }); + + it('limits to the event that is passed in', () => { + expect(emitter.listenerCount()).toBe(0); + emitter.on('a', () => {}); + expect(emitter.listenerCount('a')).toBe(1); + emitter.on('a', () => {}); + expect(emitter.listenerCount('a')).toBe(2); + emitter.on('b', () => {}); + expect(emitter.listenerCount('a')).toBe(2); + expect(emitter.listenerCount('b')).toBe(1); + expect(emitter.listenerCount()).toBe(3); + }); + }); + + describe('#on', () => { + it('registers a handler', () => { + const handler = sinon.stub(); + emitter.on('a', handler); + expect(emitter.listenerCount('a')).toBe(1); + + expect(handler.callCount).toBe(0); + emitter.emit('a'); + expect(handler.callCount).toBe(1); + }); + + it('allows multiple event handlers for the same event', () => { + emitter.on('a', () => {}); + emitter.on('a', () => {}); + expect(emitter.listenerCount('a')).toBe(2); + }); + + it('allows the same function to be registered multiple times', () => { + const handler = () => {}; + emitter.on('a', handler); + expect(emitter.listenerCount()).toBe(1); + emitter.on('a', handler); + expect(emitter.listenerCount()).toBe(2); + }); + }); + + describe('#off', () => { + it('removes a listener if it was registered', () => { + const handler = sinon.stub(); + expect(emitter.listenerCount()).toBe(0); + emitter.on('a', handler); + expect(emitter.listenerCount('a')).toBe(1); + emitter.off('a', handler); + expect(emitter.listenerCount('a')).toBe(0); + }); + + it('clears all listeners if no handler is passed', () => { + emitter.on('a', () => {}); + emitter.on('a', () => {}); + expect(emitter.listenerCount()).toBe(2); + emitter.off('a'); + expect(emitter.listenerCount()).toBe(0); + }); + + it('does not mind if the listener is not registered', () => { + emitter.off('a', () => {}); + }); + + it('does not mind if the event has no listeners', () => { + emitter.off('a'); + }); + }); + + describe('#emit', () => { + it('calls the handlers in the order they were defined', () => { + let i = 0; + const incr = () => ++i; + const one = sinon.spy(incr); + const two = sinon.spy(incr); + const three = sinon.spy(incr); + const four = sinon.spy(incr); + + emitter + .on('a', one) + .on('a', two) + .on('a', three) + .on('a', four) + .emit('a'); + + expect(one).toHaveProperty('callCount', 1); + expect(one.returned(1)).toBeDefined(); + + expect(two).toHaveProperty('callCount', 1); + expect(two.returned(2)).toBeDefined(); + + expect(three).toHaveProperty('callCount', 1); + expect(three.returned(3)).toBeDefined(); + + expect(four).toHaveProperty('callCount', 1); + expect(four.returned(4)).toBeDefined(); + }); + + it('always emits the handlers that were initially registered', () => { + const destructive = sinon.spy(() => { + emitter.removeAllListeners(); + expect(emitter.listenerCount()).toBe(0); + }); + const stub = sinon.stub(); + + emitter.on('run', destructive).on('run', stub).emit('run'); + + expect(destructive).toHaveProperty('callCount', 1); + expect(stub).toHaveProperty('callCount', 1); + }); + + it('applies all arguments except the first', () => { + emitter + .on('a', (a, b, c) => { + expect(a).toBe('foo'); + expect(b).toBe('bar'); + expect(c).toBe('baz'); + }) + .emit('a', 'foo', 'bar', 'baz'); + }); + + it('uses the SimpleEmitter as the this context', () => { + emitter + .on('a', function () { + expect(this).toBe(emitter); + }) + .emit('a'); + }); + }); +}); diff --git a/src/legacy/ui/public/vis/editors/default/controls/components/number_list/__snapshots__/number_list.test.tsx.snap b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/__snapshots__/number_list.test.tsx.snap index ab192e6fd3cb..4004f8627a89 100644 --- a/src/legacy/ui/public/vis/editors/default/controls/components/number_list/__snapshots__/number_list.test.tsx.snap +++ b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/__snapshots__/number_list.test.tsx.snap @@ -18,7 +18,7 @@ exports[`NumberList should be rendered with default set of props 1`] = ` onChange={[Function]} onDelete={[Function]} range={ - Range { + NumberListRange { "max": 10, "maxInclusive": true, "min": 1, @@ -45,7 +45,7 @@ exports[`NumberList should be rendered with default set of props 1`] = ` onChange={[Function]} onDelete={[Function]} range={ - Range { + NumberListRange { "max": 10, "maxInclusive": true, "min": 1, diff --git a/src/legacy/ui/public/vis/editors/default/controls/components/number_list/number_row.tsx b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/number_row.tsx index 23e671180e98..777b0a94f0f3 100644 --- a/src/legacy/ui/public/vis/editors/default/controls/components/number_list/number_row.tsx +++ b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/number_row.tsx @@ -21,7 +21,7 @@ import React, { useCallback } from 'react'; import { EuiFieldNumber, EuiFlexGroup, EuiFlexItem, EuiButtonIcon } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { Range } from '../../../../../../utils/range'; +import { NumberListRange } from './range'; export interface NumberRowProps { autoFocus: boolean; @@ -29,7 +29,7 @@ export interface NumberRowProps { isInvalid: boolean; labelledbyId: string; model: NumberRowModel; - range: Range; + range: NumberListRange; onBlur(): void; onChange({ id, value }: { id: string; value: string }): void; onDelete(index: string): void; diff --git a/src/legacy/ui/public/utils/__tests__/range.js b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/range.test.ts similarity index 66% rename from src/legacy/ui/public/utils/__tests__/range.js rename to src/legacy/ui/public/vis/editors/default/controls/components/number_list/range.test.ts index e7947894d3e2..e9090e5b38ef 100644 --- a/src/legacy/ui/public/utils/__tests__/range.js +++ b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/range.test.ts @@ -17,32 +17,30 @@ * under the License. */ -import _ from 'lodash'; -import expect from '@kbn/expect'; -import { parseRange } from '../range'; +import { forOwn } from 'lodash'; +import { parseRange } from './range'; -describe('Range parsing utility', function () { - - it('throws an error for inputs that are not formatted properly', function () { - expect(function () { +describe('Range parsing utility', () => { + test('throws an error for inputs that are not formatted properly', () => { + expect(() => { parseRange(''); - }).to.throwException(TypeError); + }).toThrowError(TypeError); - expect(function () { + expect(function() { parseRange('p10202'); - }).to.throwException(TypeError); + }).toThrowError(TypeError); - expect(function () { + expect(function() { parseRange('{0,100}'); - }).to.throwException(TypeError); + }).toThrowError(TypeError); - expect(function () { + expect(function() { parseRange('[0,100'); - }).to.throwException(TypeError); + }).toThrowError(TypeError); - expect(function () { + expect(function() { parseRange(')0,100('); - }).to.throwException(TypeError); + }).toThrowError(TypeError); }); const tests = { @@ -51,52 +49,52 @@ describe('Range parsing utility', function () { min: 0, max: 100, minInclusive: true, - maxInclusive: true + maxInclusive: true, }, within: [ [0, true], [0.0000001, true], [1, true], [99.99999, true], - [100, true] - ] + [100, true], + ], }, '(26.3 , 42]': { props: { min: 26.3, max: 42, minInclusive: false, - maxInclusive: true + maxInclusive: true, }, within: [ [26.2999999, false], [26.3000001, true], [30, true], [41, true], - [42, true] - ] + [42, true], + ], }, '(-50,50)': { props: { min: -50, max: 50, minInclusive: false, - maxInclusive: false + maxInclusive: false, }, within: [ [-50, false], [-49.99999, true], [0, true], [49.99999, true], - [50, false] - ] + [50, false], + ], }, '(Infinity, -Infinity)': { props: { min: -Infinity, max: Infinity, minInclusive: false, - maxInclusive: false + maxInclusive: false, }, within: [ [0, true], @@ -105,25 +103,24 @@ describe('Range parsing utility', function () { [-10000000000, true], [-Infinity, false], [Infinity, false], - ] - } + ], + }, }; - _.forOwn(tests, function (spec, str) { - - describe(str, function () { + forOwn(tests, (spec, str: any) => { + // eslint-disable-next-line jest/valid-describe + describe(str, () => { const range = parseRange(str); - it('creation', function () { - expect(range).to.eql(spec.props); + it('creation', () => { + expect(range).toEqual(spec.props); }); - spec.within.forEach(function (tup) { - it('#within(' + tup[0] + ')', function () { - expect(range.within(tup[0])).to.be(tup[1]); + spec.within.forEach((tup: any[]) => { + it('#within(' + tup[0] + ')', () => { + expect(range.within(tup[0])).toBe(tup[1]); }); }); }); - }); }); diff --git a/src/legacy/ui/public/utils/range.js b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/range.ts similarity index 59% rename from src/legacy/ui/public/utils/range.js rename to src/legacy/ui/public/vis/editors/default/controls/components/number_list/range.ts index 54bd1b190334..da3b7a61aea9 100644 --- a/src/legacy/ui/public/utils/range.js +++ b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/range.ts @@ -17,8 +17,6 @@ * under the License. */ -import _ from 'lodash'; - /** * Regexp portion that matches our number * @@ -44,41 +42,44 @@ const _RE_NUMBER = '(\\-?(?:\\d+(?:\\.\\d+)?|Infinity))'; * * @type {RegExp} */ -const RANGE_RE = new RegExp('^\\s*([\\[|\\(])\\s*' + _RE_NUMBER + '\\s*,\\s*' + _RE_NUMBER + '\\s*([\\]|\\)])\\s*$'); +const RANGE_RE = new RegExp( + '^\\s*([\\[|\\(])\\s*' + _RE_NUMBER + '\\s*,\\s*' + _RE_NUMBER + '\\s*([\\]|\\)])\\s*$' +); + +export class NumberListRange { + constructor( + public minInclusive: boolean, + public min: number, + public max: number, + public maxInclusive: boolean + ) {} -export function parseRange(input) { + within(n: number): boolean { + if ((this.min === n && !this.minInclusive) || this.min > n) return false; + if ((this.max === n && !this.maxInclusive) || this.max < n) return false; + + return true; + } +} +export function parseRange(input: string): NumberListRange { const match = String(input).match(RANGE_RE); if (!match) { throw new TypeError('expected input to be in interval notation e.g., (100, 200]'); } - return new Range( - match[1] === '[', - parseFloat(match[2]), - parseFloat(match[3]), - match[4] === ']' - ); -} - -function Range(/* minIncl, min, max, maxIncl */) { - const args = _.toArray(arguments); - if (args[1] > args[2]) args.reverse(); + const args = [match[1] === '[', parseFloat(match[2]), parseFloat(match[3]), match[4] === ']']; - this.minInclusive = args[0]; - this.min = args[1]; - this.max = args[2]; - this.maxInclusive = args[3]; -} - -Range.prototype.within = function (n) { - if (this.min === n && !this.minInclusive) return false; - if (this.min > n) return false; - - if (this.max === n && !this.maxInclusive) return false; - if (this.max < n) return false; - - return true; -}; + if (args[1] > args[2]) { + args.reverse(); + } + const [minInclusive, min, max, maxInclusive] = args; + return new NumberListRange( + minInclusive as boolean, + min as number, + max as number, + maxInclusive as boolean + ); +} diff --git a/src/legacy/ui/public/vis/editors/default/controls/components/number_list/utils.test.ts b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/utils.test.ts index c6772cc10876..89fb5738db37 100644 --- a/src/legacy/ui/public/vis/editors/default/controls/components/number_list/utils.test.ts +++ b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/utils.test.ts @@ -27,12 +27,12 @@ import { getNextModel, getRange, } from './utils'; -import { Range } from '../../../../../../utils/range'; +import { NumberListRange } from './range'; import { NumberRowModel } from './number_row'; describe('NumberList utils', () => { let modelList: NumberRowModel[]; - let range: Range; + let range: NumberListRange; beforeEach(() => { modelList = [ diff --git a/src/legacy/ui/public/vis/editors/default/controls/components/number_list/utils.ts b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/utils.ts index 563e8f0a6a9b..399253f27445 100644 --- a/src/legacy/ui/public/vis/editors/default/controls/components/number_list/utils.ts +++ b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/utils.ts @@ -21,7 +21,7 @@ import { last } from 'lodash'; import { i18n } from '@kbn/i18n'; import { htmlIdGenerator } from '@elastic/eui'; -import { parseRange, Range } from '../../../../../../utils/range'; +import { parseRange, NumberListRange } from './range'; import { NumberRowModel } from './number_row'; const EMPTY_STRING = ''; @@ -34,7 +34,7 @@ function parse(value: string) { return isNaN(parsedValue) ? EMPTY_STRING : parsedValue; } -function getRange(range?: string): Range { +function getRange(range?: string): NumberListRange { try { return range ? parseRange(range) : defaultRange; } catch (e) { @@ -42,7 +42,7 @@ function getRange(range?: string): Range { } } -function validateValue(value: number | '', numberRange: Range) { +function validateValue(value: number | '', numberRange: NumberListRange) { const result: { isInvalid: boolean; error?: string } = { isInvalid: false, }; @@ -76,7 +76,7 @@ function validateOrder(list: Array) { return result; } -function getNextModel(list: NumberRowModel[], range: Range): NumberRowModel { +function getNextModel(list: NumberRowModel[], range: NumberListRange): NumberRowModel { const lastValue = last(list).value; let next = Number(lastValue) ? Number(lastValue) + 1 : 1; @@ -104,7 +104,7 @@ function getInitModelList(list: Array): NumberRowModel[] { function getUpdatedModels( numberList: Array, modelList: NumberRowModel[], - numberRange: Range, + numberRange: NumberListRange, invalidOrderModelIndex?: number ): NumberRowModel[] { if (!numberList.length) { diff --git a/src/legacy/ui/public/vis/map/convert_to_geojson.js b/src/legacy/ui/public/vis/map/convert_to_geojson.js index 77896490678f..14c282b58bed 100644 --- a/src/legacy/ui/public/vis/map/convert_to_geojson.js +++ b/src/legacy/ui/public/vis/map/convert_to_geojson.js @@ -17,10 +17,9 @@ * under the License. */ -import { decodeGeoHash } from 'ui/utils/decode_geo_hash'; +import { decodeGeoHash } from './decode_geo_hash'; import { gridDimensions } from './grid_dimensions'; - export function convertToGeoJson(tabifiedResponse, { geohash, geocentroid, metric }) { let features; diff --git a/src/legacy/ui/public/utils/__tests__/decode_geo_hash.test.js b/src/legacy/ui/public/vis/map/decode_geo_hash.test.ts similarity index 75% rename from src/legacy/ui/public/utils/__tests__/decode_geo_hash.test.js rename to src/legacy/ui/public/vis/map/decode_geo_hash.test.ts index 1ffe9ca7b4df..c1ca7e4c8038 100644 --- a/src/legacy/ui/public/utils/__tests__/decode_geo_hash.test.js +++ b/src/legacy/ui/public/vis/map/decode_geo_hash.test.ts @@ -17,27 +17,18 @@ * under the License. */ -import { geohashColumns, decodeGeoHash } from '../decode_geo_hash'; +import { geohashColumns, decodeGeoHash } from './decode_geo_hash'; -test('geohashColumns', function () { +test('geohashColumns', () => { expect(geohashColumns(1)).toBe(8); expect(geohashColumns(2)).toBe(8 * 4); expect(geohashColumns(3)).toBe(8 * 4 * 8); expect(geohashColumns(4)).toBe(8 * 4 * 8 * 4); }); -test('decodeGeoHash', function () { +test('decodeGeoHash', () => { expect(decodeGeoHash('drm3btev3e86')).toEqual({ - latitude: [ - 41.119999922811985, - 41.12000009045005, - 41.12000000663102, - ], - longitude: [ - -71.34000029414892, - -71.3399999588728, - -71.34000012651086, - ], + latitude: [41.119999922811985, 41.12000009045005, 41.12000000663102], + longitude: [-71.34000029414892, -71.3399999588728, -71.34000012651086], }); }); - diff --git a/src/legacy/ui/public/utils/decode_geo_hash.ts b/src/legacy/ui/public/vis/map/decode_geo_hash.ts similarity index 100% rename from src/legacy/ui/public/utils/decode_geo_hash.ts rename to src/legacy/ui/public/vis/map/decode_geo_hash.ts diff --git a/src/legacy/ui/public/vis/map/kibana_map.js b/src/legacy/ui/public/vis/map/kibana_map.js index dc57809b6570..cb618444af7c 100644 --- a/src/legacy/ui/public/vis/map/kibana_map.js +++ b/src/legacy/ui/public/vis/map/kibana_map.js @@ -22,7 +22,7 @@ import { createZoomWarningMsg } from './map_messages'; import L from 'leaflet'; import $ from 'jquery'; import _ from 'lodash'; -import { zoomToPrecision } from '../../utils/zoom_to_precision'; +import { zoomToPrecision } from './zoom_to_precision'; import { i18n } from '@kbn/i18n'; import { ORIGIN } from '../../../../core_plugins/tile_map/common/origin'; diff --git a/src/legacy/ui/public/utils/zoom_to_precision.js b/src/legacy/ui/public/vis/map/zoom_to_precision.ts similarity index 52% rename from src/legacy/ui/public/utils/zoom_to_precision.js rename to src/legacy/ui/public/vis/map/zoom_to_precision.ts index f5c16b640d12..552c50959028 100644 --- a/src/legacy/ui/public/utils/zoom_to_precision.js +++ b/src/legacy/ui/public/vis/map/zoom_to_precision.ts @@ -19,39 +19,42 @@ import { geohashColumns } from './decode_geo_hash'; -const maxPrecision = 12; -/** - * Map Leaflet zoom levels to geohash precision levels. - * The size of a geohash column-width on the map should be at least `minGeohashPixels` pixels wide. - */ - - +const defaultMaxPrecision = 12; +const minGeoHashPixels = 16; - -const zoomPrecisionMap = {}; -const minGeohashPixels = 16; - -function calculateZoomToPrecisionMap(maxZoom) { +const calculateZoomToPrecisionMap = (maxZoom: number): Map => { + /** + * Map Leaflet zoom levels to geohash precision levels. + * The size of a geohash column-width on the map should be at least `minGeohashPixels` pixels wide. + */ + const zoomPrecisionMap = new Map(); for (let zoom = 0; zoom <= maxZoom; zoom += 1) { - if (typeof zoomPrecisionMap[zoom] === 'number') { + if (typeof zoomPrecisionMap.get(zoom) === 'number') { continue; } + const worldPixels = 256 * Math.pow(2, zoom); - zoomPrecisionMap[zoom] = 1; - for (let precision = 2; precision <= maxPrecision; precision += 1) { + + zoomPrecisionMap.set(zoom, 1); + + for (let precision = 2; precision <= defaultMaxPrecision; precision += 1) { const columns = geohashColumns(precision); - if ((worldPixels / columns) >= minGeohashPixels) { - zoomPrecisionMap[zoom] = precision; + + if (worldPixels / columns >= minGeoHashPixels) { + zoomPrecisionMap.set(zoom, precision); } else { break; } } } -} + return zoomPrecisionMap; +}; + +export function zoomToPrecision(mapZoom: number, maxPrecision: number, maxZoom: number) { + const zoomPrecisionMap = calculateZoomToPrecisionMap(typeof maxZoom === 'number' ? maxZoom : 21); + const precision = zoomPrecisionMap.get(mapZoom); -export function zoomToPrecision(mapZoom, maxPrecision, maxZoom) { - calculateZoomToPrecisionMap(typeof maxZoom === 'number' ? maxZoom : 21); - return Math.min(zoomPrecisionMap[mapZoom], maxPrecision); + return precision ? Math.min(precision, maxPrecision) : maxPrecision; } diff --git a/src/plugins/dashboard_embeddable_container/public/embeddable/dashboard_container.tsx b/src/plugins/dashboard_embeddable_container/public/embeddable/dashboard_container.tsx index 684aa93779bc..021a1a9d1e64 100644 --- a/src/plugins/dashboard_embeddable_container/public/embeddable/dashboard_container.tsx +++ b/src/plugins/dashboard_embeddable_container/public/embeddable/dashboard_container.tsx @@ -90,6 +90,8 @@ export type DashboardReactContext = KibanaReactContext { public readonly type = DASHBOARD_CONTAINER_TYPE; + public renderEmpty?: undefined | (() => React.ReactNode); + constructor( initialInput: DashboardContainerInput, private readonly options: DashboardContainerOptions, @@ -124,7 +126,7 @@ export class DashboardContainer extends Container - + , dom diff --git a/src/plugins/dashboard_embeddable_container/public/embeddable/grid/_dashboard_grid.scss b/src/plugins/dashboard_embeddable_container/public/embeddable/grid/_dashboard_grid.scss index 24b813ec5896..0bd356522c7f 100644 --- a/src/plugins/dashboard_embeddable_container/public/embeddable/grid/_dashboard_grid.scss +++ b/src/plugins/dashboard_embeddable_container/public/embeddable/grid/_dashboard_grid.scss @@ -34,7 +34,7 @@ .dshLayout-isMaximizedPanel { height: 100% !important; /* 1. */ width: 100%; - position: absolute; + position: absolute !important; } /** diff --git a/src/plugins/dashboard_embeddable_container/public/embeddable/viewport/dashboard_viewport.test.tsx b/src/plugins/dashboard_embeddable_container/public/embeddable/viewport/dashboard_viewport.test.tsx index a2f7b8dc28fb..e3d9b8552f06 100644 --- a/src/plugins/dashboard_embeddable_container/public/embeddable/viewport/dashboard_viewport.test.tsx +++ b/src/plugins/dashboard_embeddable_container/public/embeddable/viewport/dashboard_viewport.test.tsx @@ -121,6 +121,24 @@ test('renders DashboardViewport with no visualizations', () => { component.unmount(); }); +test('renders DashboardEmptyScreen', () => { + const renderEmptyScreen = jest.fn(); + const { props, options } = getProps({ renderEmpty: renderEmptyScreen }); + props.container.updateInput({ isEmptyState: true }); + const component = mount( + + + + + + ); + const dashboardEmptyScreenDiv = component.find('.dshDashboardEmptyScreen'); + expect(dashboardEmptyScreenDiv.length).toBe(1); + expect(renderEmptyScreen).toHaveBeenCalled(); + + component.unmount(); +}); + test('renders exit full screen button when in full screen mode', async () => { const { props, options } = getProps(); props.container.updateInput({ isFullScreenMode: true }); @@ -153,6 +171,39 @@ test('renders exit full screen button when in full screen mode', async () => { component.unmount(); }); +test('renders exit full screen button when in full screen mode and empty screen', async () => { + const renderEmptyScreen = jest.fn(); + renderEmptyScreen.mockReturnValue(React.createElement('div')); + const { props, options } = getProps({ renderEmpty: renderEmptyScreen }); + props.container.updateInput({ isEmptyState: true, isFullScreenMode: true }); + const component = mount( + + + + + + ); + expect( + (component + .find('.dshDashboardEmptyScreen') + .childAt(0) + .type() as any).name + ).toBe('ExitFullScreenButton'); + + props.container.updateInput({ isFullScreenMode: false }); + component.update(); + await nextTick(); + + expect( + (component + .find('.dshDashboardEmptyScreen') + .childAt(0) + .type() as any).name + ).not.toBe('ExitFullScreenButton'); + + component.unmount(); +}); + test('DashboardViewport unmount unsubscribes', async done => { const { props, options } = getProps(); const component = mount( diff --git a/src/plugins/dashboard_embeddable_container/public/embeddable/viewport/dashboard_viewport.tsx b/src/plugins/dashboard_embeddable_container/public/embeddable/viewport/dashboard_viewport.tsx index 13407e5e3372..e7fd379898dd 100644 --- a/src/plugins/dashboard_embeddable_container/public/embeddable/viewport/dashboard_viewport.tsx +++ b/src/plugins/dashboard_embeddable_container/public/embeddable/viewport/dashboard_viewport.tsx @@ -26,6 +26,7 @@ import { context } from '../../../../kibana_react/public'; export interface DashboardViewportProps { container: DashboardContainer; + renderEmpty?: () => React.ReactNode; } interface State { @@ -34,6 +35,7 @@ interface State { title: string; description?: string; panels: { [key: string]: PanelState }; + isEmptyState?: boolean; } export class DashboardViewport extends React.Component { @@ -44,26 +46,40 @@ export class DashboardViewport extends React.Component { - const { isFullScreenMode, useMargins, title, description } = this.props.container.getInput(); + const { + isFullScreenMode, + useMargins, + title, + description, + isEmptyState, + } = this.props.container.getInput(); if (this.mounted) { this.setState({ isFullScreenMode, description, useMargins, title, + isEmptyState, }); } }); @@ -82,19 +98,33 @@ export class DashboardViewport extends React.Component + {isFullScreenMode && ( + + )} + {renderEmpty && renderEmpty()} + + ); + } + + private renderContainerScreen() { const { container } = this.props; + const { isFullScreenMode, panels, title, description, useMargins } = this.state; return (
- {this.state.isFullScreenMode && ( + {isFullScreenMode && ( @@ -103,4 +133,13 @@ export class DashboardViewport extends React.Component ); } + + public render() { + return ( + + {this.state.isEmptyState ? this.renderEmptyScreen() : null} + {this.renderContainerScreen()} + + ); + } } diff --git a/src/plugins/dashboard_embeddable_container/public/plugin.tsx b/src/plugins/dashboard_embeddable_container/public/plugin.tsx index 79cc9b698054..d18fbba239ec 100644 --- a/src/plugins/dashboard_embeddable_container/public/plugin.tsx +++ b/src/plugins/dashboard_embeddable_container/public/plugin.tsx @@ -27,7 +27,7 @@ import { ExpandPanelAction, ReplacePanelAction } from '.'; import { DashboardContainerFactory } from './embeddable/dashboard_container_factory'; import { Start as InspectorStartContract } from '../../../plugins/inspector/public'; import { - SavedObjectFinder as SavedObjectFinderUi, + SavedObjectFinderUi, SavedObjectFinderProps, ExitFullScreenButton as ExitFullScreenButtonUi, ExitFullScreenButtonProps, diff --git a/src/plugins/embeddable/public/lib/containers/container.ts b/src/plugins/embeddable/public/lib/containers/container.ts index bce16747ed48..71e7cca3552b 100644 --- a/src/plugins/embeddable/public/lib/containers/container.ts +++ b/src/plugins/embeddable/public/lib/containers/container.ts @@ -240,6 +240,7 @@ export abstract class Container< ...this.input.panels, [panelState.explicitInput.id]: panelState, }, + isEmptyState: false, } as Partial); return await this.untilEmbeddableLoaded(panelState.explicitInput.id); diff --git a/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts b/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts index 33cb146a056c..019758277894 100644 --- a/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts +++ b/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts @@ -28,7 +28,7 @@ export interface EmbeddableInput { id: string; lastReloadRequestTime?: number; hidePanelTitles?: boolean; - + isEmptyState?: boolean; /** * List of action IDs that this embeddable should not render. */ diff --git a/src/plugins/es_ui_shared/public/request/np_ready_request.ts b/src/plugins/es_ui_shared/public/request/np_ready_request.ts index 48c7904661e5..5a3f28ed7648 100644 --- a/src/plugins/es_ui_shared/public/request/np_ready_request.ts +++ b/src/plugins/es_ui_shared/public/request/np_ready_request.ts @@ -19,11 +19,12 @@ import { useEffect, useState, useRef } from 'react'; -import { HttpServiceBase } from '../../../../../src/core/public'; +import { HttpServiceBase, HttpFetchQuery } from '../../../../../src/core/public'; export interface SendRequestConfig { path: string; method: 'get' | 'post' | 'put' | 'delete' | 'patch' | 'head'; + query?: HttpFetchQuery; body?: any; } @@ -48,10 +49,10 @@ export interface UseRequestResponse { export const sendRequest = async ( httpClient: HttpServiceBase, - { path, method, body }: SendRequestConfig + { path, method, body, query }: SendRequestConfig ): Promise => { try { - const response = await httpClient[method](path, { body }); + const response = await httpClient[method](path, { body, query }); return { data: response.data ? response.data : response, @@ -70,6 +71,7 @@ export const useRequest = ( { path, method, + query, body, pollIntervalMs, initialData, @@ -112,6 +114,7 @@ export const useRequest = ( const requestBody = { path, method, + query, body, }; diff --git a/src/plugins/kibana_react/public/saved_objects/saved_object_finder.test.tsx b/src/plugins/kibana_react/public/saved_objects/saved_object_finder.test.tsx index b35ba427378a..58b396d57639 100644 --- a/src/plugins/kibana_react/public/saved_objects/saved_object_finder.test.tsx +++ b/src/plugins/kibana_react/public/saved_objects/saved_object_finder.test.tsx @@ -35,7 +35,7 @@ import { IconType } from '@elastic/eui'; import { shallow } from 'enzyme'; import React from 'react'; import * as sinon from 'sinon'; -import { SavedObjectFinder } from './saved_object_finder'; +import { SavedObjectFinderUi as SavedObjectFinder } from './saved_object_finder'; // eslint-disable-next-line import { coreMock } from '../../../../core/public/mocks'; diff --git a/src/plugins/kibana_react/public/saved_objects/saved_object_finder.tsx b/src/plugins/kibana_react/public/saved_objects/saved_object_finder.tsx index c65d42895876..51fbbd2ba304 100644 --- a/src/plugins/kibana_react/public/saved_objects/saved_object_finder.tsx +++ b/src/plugins/kibana_react/public/saved_objects/saved_object_finder.tsx @@ -46,6 +46,7 @@ import { i18n } from '@kbn/i18n'; import { SavedObjectAttributes } from '../../../../core/server'; import { SimpleSavedObject, CoreStart } from '../../../../core/public'; +import { useKibana } from '../context'; // TODO the typings for EuiListGroup are incorrect - maxWidth is missing. This can be removed when the types are adjusted const FixedEuiListGroup = (EuiListGroup as any) as React.FunctionComponent< @@ -104,12 +105,18 @@ interface SavedObjectFinderInitialPageSize extends BaseSavedObjectFinder { initialPageSize?: 5 | 10 | 15 | 25; fixedPageSize?: undefined; } -export type SavedObjectFinderProps = { + +export type SavedObjectFinderProps = SavedObjectFinderFixedPage | SavedObjectFinderInitialPageSize; + +export type SavedObjectFinderUiProps = { savedObjects: CoreStart['savedObjects']; uiSettings: CoreStart['uiSettings']; -} & (SavedObjectFinderFixedPage | SavedObjectFinderInitialPageSize); +} & SavedObjectFinderProps; -class SavedObjectFinder extends React.Component { +class SavedObjectFinderUi extends React.Component< + SavedObjectFinderUiProps, + SavedObjectFinderState +> { public static propTypes = { onChoose: PropTypes.func, noItemsMessage: PropTypes.node, @@ -174,7 +181,7 @@ class SavedObjectFinder extends React.Component { + const { services } = useKibana(); + return ( + + ); +}; + +export { SavedObjectFinder, SavedObjectFinderUi }; diff --git a/src/plugins/share/kibana.json b/src/plugins/share/kibana.json index bbe393a76c5d..dce2ac9281ab 100644 --- a/src/plugins/share/kibana.json +++ b/src/plugins/share/kibana.json @@ -1,6 +1,6 @@ { "id": "share", "version": "kibana", - "server": false, + "server": true, "ui": true } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_empty_screen_directive.ts b/src/plugins/share/server/index.ts similarity index 66% rename from src/legacy/core_plugins/kibana/public/dashboard/dashboard_empty_screen_directive.ts rename to src/plugins/share/server/index.ts index 5ebefd817ca4..9e574314f800 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_empty_screen_directive.ts +++ b/src/plugins/share/server/index.ts @@ -16,15 +16,10 @@ * specific language governing permissions and limitations * under the License. */ -// @ts-ignore -import angular from 'angular'; -import { DashboardEmptyScreen } from './dashboard_empty_screen'; -angular - .module('app/dashboard/emptyScreen', ['react']) - .directive('dashboardEmptyScreen', function(reactDirective: any) { - return reactDirective(DashboardEmptyScreen, [ - ['showLinkToVisualize', { watchDepth: 'value' }], - ['onLinkClick', { watchDepth: 'reference' }], - ]); - }); +import { PluginInitializerContext } from '../../../core/server'; +import { SharePlugin } from './plugin'; + +export function plugin(initializerContext: PluginInitializerContext) { + return new SharePlugin(initializerContext); +} diff --git a/src/legacy/server/url_shortening/routes/shorten_url.js b/src/plugins/share/server/plugin.ts similarity index 59% rename from src/legacy/server/url_shortening/routes/shorten_url.js rename to src/plugins/share/server/plugin.ts index 0203e9373384..bcb681a50652 100644 --- a/src/legacy/server/url_shortening/routes/shorten_url.js +++ b/src/plugins/share/server/plugin.ts @@ -17,19 +17,21 @@ * under the License. */ -import { handleShortUrlError } from './lib/short_url_error'; -import { shortUrlAssertValid } from './lib/short_url_assert_valid'; +import { CoreSetup, Plugin, PluginInitializerContext } from 'kibana/server'; +import { createRoutes } from './routes/create_routes'; -export const createShortenUrlRoute = ({ shortUrlLookup }) => ({ - method: 'POST', - path: '/api/shorten_url', - handler: async function (request) { - try { - shortUrlAssertValid(request.payload.url); - const urlId = await shortUrlLookup.generateUrlId(request.payload.url, request); - return { urlId }; - } catch (err) { - throw handleShortUrlError(err); - } +export class SharePlugin implements Plugin { + constructor(private readonly initializerContext: PluginInitializerContext) {} + + public async setup(core: CoreSetup) { + createRoutes(core, this.initializerContext.logger.get()); + } + + public start() { + this.initializerContext.logger.get().debug('Starting plugin'); + } + + public stop() { + this.initializerContext.logger.get().debug('Stopping plugin'); } -}); +} diff --git a/src/legacy/ui/public/utils/find_by_param.ts b/src/plugins/share/server/routes/create_routes.ts similarity index 62% rename from src/legacy/ui/public/utils/find_by_param.ts rename to src/plugins/share/server/routes/create_routes.ts index de32fc955a8c..bd4b6fdb0879 100644 --- a/src/legacy/ui/public/utils/find_by_param.ts +++ b/src/plugins/share/server/routes/create_routes.ts @@ -17,22 +17,16 @@ * under the License. */ -import _ from 'lodash'; +import { CoreSetup, Logger } from 'kibana/server'; -interface AnyObject { - [key: string]: any; -} +import { shortUrlLookupProvider } from './lib/short_url_lookup'; +import { createGotoRoute } from './goto'; +import { createShortenUrlRoute } from './shorten_url'; + +export function createRoutes({ http }: CoreSetup, logger: Logger) { + const shortUrlLookup = shortUrlLookupProvider({ logger }); + const router = http.createRouter(); -// given an object or array of objects, return the value of the passed param -// if the param is missing, return undefined -export function findByParam(values: AnyObject | AnyObject[], param: string) { - if (Array.isArray(values)) { - // point series chart - const index = _.findIndex(values, param); - if (index === -1) { - return; - } - return values[index][param]; - } - return values[param]; // pie chart + createGotoRoute({ router, shortUrlLookup, http }); + createShortenUrlRoute({ router, shortUrlLookup }); } diff --git a/src/plugins/share/server/routes/goto.ts b/src/plugins/share/server/routes/goto.ts new file mode 100644 index 000000000000..7343dc1bd34a --- /dev/null +++ b/src/plugins/share/server/routes/goto.ts @@ -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 { CoreSetup, IRouter } from 'kibana/server'; +import { schema } from '@kbn/config-schema'; + +import { shortUrlAssertValid } from './lib/short_url_assert_valid'; +import { ShortUrlLookupService } from './lib/short_url_lookup'; + +export const createGotoRoute = ({ + router, + shortUrlLookup, + http, +}: { + router: IRouter; + shortUrlLookup: ShortUrlLookupService; + http: CoreSetup['http']; +}) => { + router.get( + { + path: '/goto/{urlId}', + validate: { + params: schema.object({ urlId: schema.string() }), + }, + }, + router.handleLegacyErrors(async function(context, request, response) { + const url = await shortUrlLookup.getUrl(request.params.urlId, { + savedObjects: context.core.savedObjects.client, + }); + shortUrlAssertValid(url); + + const uiSettings = context.core.uiSettings.client; + const stateStoreInSessionStorage = await uiSettings.get('state:storeInSessionStorage'); + if (!stateStoreInSessionStorage) { + return response.redirected({ + headers: { + location: http.basePath.prepend(url), + }, + }); + } + return response.redirected({ + headers: { + location: http.basePath.prepend('/goto_LP/' + request.params.urlId), + }, + }); + }) + ); +}; diff --git a/src/plugins/share/server/routes/lib/short_url_assert_valid.test.ts b/src/plugins/share/server/routes/lib/short_url_assert_valid.test.ts new file mode 100644 index 000000000000..f83073e6aefe --- /dev/null +++ b/src/plugins/share/server/routes/lib/short_url_assert_valid.test.ts @@ -0,0 +1,63 @@ +/* + * 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 { shortUrlAssertValid } from './short_url_assert_valid'; + +describe('shortUrlAssertValid()', () => { + const invalid = [ + ['protocol', 'http://localhost:5601/app/kibana'], + ['protocol', 'https://localhost:5601/app/kibana'], + ['protocol', 'mailto:foo@bar.net'], + ['protocol', 'javascript:alert("hi")'], // eslint-disable-line no-script-url + ['hostname', 'localhost/app/kibana'], + ['hostname and port', 'local.host:5601/app/kibana'], + ['hostname and auth', 'user:pass@localhost.net/app/kibana'], + ['path traversal', '/app/../../not-kibana'], + ['deep path', '/app/kibana/foo'], + ['deep path', '/app/kibana/foo/bar'], + ['base path', '/base/app/kibana'], + ]; + + invalid.forEach(([desc, url]) => { + it(`fails when url has ${desc}`, () => { + try { + shortUrlAssertValid(url); + throw new Error(`expected assertion to throw`); + } catch (err) { + if (!err || !err.isBoom) { + throw err; + } + } + }); + }); + + const valid = [ + '/app/kibana', + '/app/monitoring#angular/route', + '/app/text#document-id', + '/app/some?with=query', + '/app/some?with=query#and-a-hash', + ]; + + valid.forEach(url => { + it(`allows ${url}`, () => { + shortUrlAssertValid(url); + }); + }); +}); diff --git a/src/plugins/share/server/routes/lib/short_url_assert_valid.ts b/src/plugins/share/server/routes/lib/short_url_assert_valid.ts new file mode 100644 index 000000000000..2f120bbc03cd --- /dev/null +++ b/src/plugins/share/server/routes/lib/short_url_assert_valid.ts @@ -0,0 +1,41 @@ +/* + * 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 { parse } from 'url'; +import { trim } from 'lodash'; +import Boom from 'boom'; + +export function shortUrlAssertValid(url: string) { + const { protocol, hostname, pathname } = parse(url); + + if (protocol) { + throw Boom.notAcceptable(`Short url targets cannot have a protocol, found "${protocol}"`); + } + + if (hostname) { + throw Boom.notAcceptable(`Short url targets cannot have a hostname, found "${hostname}"`); + } + + const pathnameParts = trim(pathname, '/').split('/'); + if (pathnameParts.length !== 2) { + throw Boom.notAcceptable( + `Short url target path must be in the format "/app/{{appId}}", found "${pathname}"` + ); + } +} diff --git a/src/plugins/share/server/routes/lib/short_url_lookup.test.ts b/src/plugins/share/server/routes/lib/short_url_lookup.test.ts new file mode 100644 index 000000000000..87e2b7b726e5 --- /dev/null +++ b/src/plugins/share/server/routes/lib/short_url_lookup.test.ts @@ -0,0 +1,125 @@ +/* + * 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 { shortUrlLookupProvider, ShortUrlLookupService } from './short_url_lookup'; +import { SavedObjectsClientContract, Logger } from 'kibana/server'; +import { SavedObjectsClient } from '../../../../../core/server'; + +describe('shortUrlLookupProvider', () => { + const ID = 'bf00ad16941fc51420f91a93428b27a0'; + const TYPE = 'url'; + const URL = 'http://elastic.co'; + + let savedObjects: jest.Mocked; + let deps: { savedObjects: SavedObjectsClientContract }; + let shortUrl: ShortUrlLookupService; + + beforeEach(() => { + savedObjects = ({ + get: jest.fn(), + create: jest.fn(() => Promise.resolve({ id: ID })), + update: jest.fn(), + errors: SavedObjectsClient.errors, + } as unknown) as jest.Mocked; + + deps = { savedObjects }; + shortUrl = shortUrlLookupProvider({ logger: ({ warn: () => {} } as unknown) as Logger }); + }); + + describe('generateUrlId', () => { + it('returns the document id', async () => { + const id = await shortUrl.generateUrlId(URL, deps); + expect(id).toEqual(ID); + }); + + it('provides correct arguments to savedObjectsClient', async () => { + await shortUrl.generateUrlId(URL, { savedObjects }); + + expect(savedObjects.create).toHaveBeenCalledTimes(1); + const [type, attributes, options] = savedObjects.create.mock.calls[0]; + + expect(type).toEqual(TYPE); + expect(Object.keys(attributes).sort()).toEqual([ + 'accessCount', + 'accessDate', + 'createDate', + 'url', + ]); + expect(attributes.url).toEqual(URL); + expect(options!.id).toEqual(ID); + }); + + it('passes persists attributes', async () => { + await shortUrl.generateUrlId(URL, deps); + + expect(savedObjects.create).toHaveBeenCalledTimes(1); + const [type, attributes] = savedObjects.create.mock.calls[0]; + + expect(type).toEqual(TYPE); + expect(Object.keys(attributes).sort()).toEqual([ + 'accessCount', + 'accessDate', + 'createDate', + 'url', + ]); + expect(attributes.url).toEqual(URL); + }); + + it('gracefully handles version conflict', async () => { + const error = savedObjects.errors.decorateConflictError(new Error()); + savedObjects.create.mockImplementation(() => { + throw error; + }); + const id = await shortUrl.generateUrlId(URL, deps); + expect(id).toEqual(ID); + }); + }); + + describe('getUrl', () => { + beforeEach(() => { + const attributes = { accessCount: 2, url: URL }; + savedObjects.get.mockResolvedValue({ id: ID, attributes, type: 'url', references: [] }); + }); + + it('provides the ID to savedObjectsClient', async () => { + await shortUrl.getUrl(ID, { savedObjects }); + + expect(savedObjects.get).toHaveBeenCalledTimes(1); + expect(savedObjects.get).toHaveBeenCalledWith(TYPE, ID); + }); + + it('returns the url', async () => { + const response = await shortUrl.getUrl(ID, deps); + expect(response).toEqual(URL); + }); + + it('increments accessCount', async () => { + await shortUrl.getUrl(ID, { savedObjects }); + + expect(savedObjects.update).toHaveBeenCalledTimes(1); + + const [type, id, attributes] = savedObjects.update.mock.calls[0]; + + expect(type).toEqual(TYPE); + expect(id).toEqual(ID); + expect(Object.keys(attributes).sort()).toEqual(['accessCount', 'accessDate']); + expect(attributes.accessCount).toEqual(3); + }); + }); +}); diff --git a/src/plugins/share/server/routes/lib/short_url_lookup.ts b/src/plugins/share/server/routes/lib/short_url_lookup.ts new file mode 100644 index 000000000000..0d8a9c86621d --- /dev/null +++ b/src/plugins/share/server/routes/lib/short_url_lookup.ts @@ -0,0 +1,84 @@ +/* + * 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 crypto from 'crypto'; +import { get } from 'lodash'; + +import { Logger, SavedObject, SavedObjectsClientContract } from 'kibana/server'; + +export interface ShortUrlLookupService { + generateUrlId(url: string, deps: { savedObjects: SavedObjectsClientContract }): Promise; + getUrl(url: string, deps: { savedObjects: SavedObjectsClientContract }): Promise; +} + +export function shortUrlLookupProvider({ logger }: { logger: Logger }): ShortUrlLookupService { + async function updateMetadata( + doc: SavedObject, + { savedObjects }: { savedObjects: SavedObjectsClientContract } + ) { + try { + await savedObjects.update('url', doc.id, { + accessDate: new Date().valueOf(), + accessCount: get(doc, 'attributes.accessCount', 0) + 1, + }); + } catch (error) { + logger.warn('Warning: Error updating url metadata'); + logger.warn(error); + // swallow errors. It isn't critical if there is no update. + } + } + + return { + async generateUrlId(url, { savedObjects }) { + const id = crypto + .createHash('md5') + .update(url) + .digest('hex'); + const { isConflictError } = savedObjects.errors; + + try { + const doc = await savedObjects.create( + 'url', + { + url, + accessCount: 0, + createDate: new Date().valueOf(), + accessDate: new Date().valueOf(), + }, + { id } + ); + + return doc.id; + } catch (error) { + if (isConflictError(error)) { + return id; + } + + throw error; + } + }, + + async getUrl(id, { savedObjects }) { + const doc = await savedObjects.get('url', id); + updateMetadata(doc, { savedObjects }); + + return doc.attributes.url; + }, + }; +} diff --git a/src/plugins/share/server/routes/shorten_url.ts b/src/plugins/share/server/routes/shorten_url.ts new file mode 100644 index 000000000000..116b90c6971c --- /dev/null +++ b/src/plugins/share/server/routes/shorten_url.ts @@ -0,0 +1,48 @@ +/* + * 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 { IRouter } from 'kibana/server'; +import { schema } from '@kbn/config-schema'; + +import { shortUrlAssertValid } from './lib/short_url_assert_valid'; +import { ShortUrlLookupService } from './lib/short_url_lookup'; + +export const createShortenUrlRoute = ({ + shortUrlLookup, + router, +}: { + shortUrlLookup: ShortUrlLookupService; + router: IRouter; +}) => { + router.post( + { + path: '/api/shorten_url', + validate: { + body: schema.object({ url: schema.string() }), + }, + }, + router.handleLegacyErrors(async function(context, request, response) { + shortUrlAssertValid(request.body.url); + const urlId = await shortUrlLookup.generateUrlId(request.body.url, { + savedObjects: context.core.savedObjects.client, + }); + return response.ok({ body: { urlId } }); + }) + ); +}; diff --git a/test/examples/README.md b/test/examples/README.md new file mode 100644 index 000000000000..44656f949bc7 --- /dev/null +++ b/test/examples/README.md @@ -0,0 +1,23 @@ +# Example plugin functional tests + +This folder contains functional tests for the example plugins. + +## Run the test + +To run these tests during development you can use the following commands: + +``` +# Start the test server (can continue running) +node scripts/functional_tests_server.js --config test/examples/config.js +# Start a test run +node scripts/functional_test_runner.js --config test/examples/config.js +``` + +## Run Kibana with a test plugin + +In case you want to start Kibana with the example plugins, you can just run: + +``` +yarn start --run-examples +``` + diff --git a/test/examples/config.js b/test/examples/config.js new file mode 100644 index 000000000000..b954390dc54a --- /dev/null +++ b/test/examples/config.js @@ -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 path from 'path'; +import { services } from '../plugin_functional/services'; + +export default async function ({ readConfigFile }) { + const functionalConfig = await readConfigFile(require.resolve('../functional/config')); + + return { + testFiles: [ + require.resolve('./search'), + ], + services: { + ...functionalConfig.get('services'), + ...services, + }, + pageObjects: functionalConfig.get('pageObjects'), + servers: functionalConfig.get('servers'), + esTestCluster: functionalConfig.get('esTestCluster'), + apps: functionalConfig.get('apps'), + esArchiver: { + directory: path.resolve(__dirname, '../es_archives') + }, + screenshots: functionalConfig.get('screenshots'), + junit: { + reportName: 'Example plugin functional tests', + }, + kbnTestServer: { + ...functionalConfig.get('kbnTestServer'), + serverArgs: [ + ...functionalConfig.get('kbnTestServer.serverArgs'), + '--run-examples', + // Required to run examples + '--env.name=development', + ], + }, + }; +} diff --git a/test/plugin_functional/test_suites/search/demo_data.ts b/test/examples/search/demo_data.ts similarity index 100% rename from test/plugin_functional/test_suites/search/demo_data.ts rename to test/examples/search/demo_data.ts diff --git a/test/plugin_functional/test_suites/search/es_search.ts b/test/examples/search/es_search.ts similarity index 100% rename from test/plugin_functional/test_suites/search/es_search.ts rename to test/examples/search/es_search.ts diff --git a/test/plugin_functional/test_suites/search/index.ts b/test/examples/search/index.ts similarity index 100% rename from test/plugin_functional/test_suites/search/index.ts rename to test/examples/search/index.ts diff --git a/test/functional/apps/dashboard/full_screen_mode.js b/test/functional/apps/dashboard/full_screen_mode.js index e18fd47b39b1..bf549ec21a6d 100644 --- a/test/functional/apps/dashboard/full_screen_mode.js +++ b/test/functional/apps/dashboard/full_screen_mode.js @@ -78,7 +78,6 @@ export default function ({ getService, getPageObjects }) { const logoButton = await PageObjects.dashboard.getExitFullScreenLogoButton(); await logoButton.moveMouseTo(); await PageObjects.dashboard.clickExitFullScreenTextButton(); - await retry.try(async () => { const isChromeVisible = await PageObjects.common.isChromeVisible(); expect(isChromeVisible).to.be(true); diff --git a/test/plugin_functional/config.js b/test/plugin_functional/config.js index a6316c607a7c..d8ce12d1fc61 100644 --- a/test/plugin_functional/config.js +++ b/test/plugin_functional/config.js @@ -33,7 +33,6 @@ export default async function ({ readConfigFile }) { require.resolve('./test_suites/app_plugins'), require.resolve('./test_suites/custom_visualizations'), require.resolve('./test_suites/panel_actions'), - require.resolve('./test_suites/search'), /** * @todo Work on re-enabling this test suite after this is merged. These tests pass diff --git a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/legacy.ts b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/legacy.ts index a310403c86b5..1928d7ac7231 100644 --- a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/legacy.ts +++ b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/legacy.ts @@ -22,7 +22,6 @@ import 'uiExports/embeddableFactories'; import 'uiExports/embeddableActions'; import { npSetup, npStart } from 'ui/new_platform'; -import { SavedObjectFinder } from 'ui/saved_objects/components/saved_object_finder'; import { ExitFullScreenButton } from 'ui/exit_full_screen'; import uiRoutes from 'ui/routes'; // @ts-ignore @@ -39,7 +38,6 @@ export const setup = pluginInstance.setup(npSetup.core, { embeddable: npSetup.plugins.embeddable, inspector: npSetup.plugins.inspector, __LEGACY: { - SavedObjectFinder, ExitFullScreenButton, }, }); @@ -64,7 +62,6 @@ export const start = pluginInstance.start(npStart.core, { inspector: npStart.plugins.inspector, uiActions: npStart.plugins.uiActions, __LEGACY: { - SavedObjectFinder, ExitFullScreenButton, onRenderComplete: (renderCompleteListener: () => void) => { if (rendered) { diff --git a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/plugin.tsx b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/plugin.tsx index 6b82a67b9fcd..adf898d9af2c 100644 --- a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/plugin.tsx +++ b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/plugin.tsx @@ -38,6 +38,10 @@ import { ContactCardEmbeddableFactory, } from './embeddable_api'; import { App } from './app'; +import { + SavedObjectFinderProps, + SavedObjectFinderUi, +} from '../../../../../../../src/plugins/kibana_react/public/saved_objects'; import { IEmbeddableStart, IEmbeddableSetup, @@ -47,7 +51,6 @@ export interface SetupDependencies { embeddable: IEmbeddableSetup; inspector: InspectorSetupContract; __LEGACY: { - SavedObjectFinder: React.ComponentType; ExitFullScreenButton: React.ComponentType; }; } @@ -57,7 +60,6 @@ interface StartDependencies { uiActions: IUiActionsStart; inspector: InspectorStartContract; __LEGACY: { - SavedObjectFinder: React.ComponentType; ExitFullScreenButton: React.ComponentType; onRenderComplete: (onRenderComplete: () => void) => void; }; @@ -99,6 +101,13 @@ export class EmbeddableExplorerPublicPlugin plugins.__LEGACY.onRenderComplete(() => { const root = document.getElementById(REACT_ROOT_ID); + const SavedObjectFinder = (props: SavedObjectFinderProps) => ( + + ); ReactDOM.render( , root diff --git a/x-pack/dev-tools/jest/create_jest_config.js b/x-pack/dev-tools/jest/create_jest_config.js index 199232262773..f8d07668d0aa 100644 --- a/x-pack/dev-tools/jest/create_jest_config.js +++ b/x-pack/dev-tools/jest/create_jest_config.js @@ -20,7 +20,8 @@ export function createJestConfig({ kibanaDirectory, xPackKibanaDirectory }) { 'uiExports/(.*)': fileMockPath, '^src/core/(.*)': `${kibanaDirectory}/src/core/$1`, '^src/legacy/(.*)': `${kibanaDirectory}/src/legacy/$1`, - '^plugins/watcher/models/(.*)': `${xPackKibanaDirectory}/legacy/plugins/watcher/public/models/$1`, + '^plugins/watcher/np_ready/application/models/(.*)': + `${xPackKibanaDirectory}/legacy/plugins/watcher/public/np_ready/application/models/$1`, '^plugins/([^/.]*)(.*)': `${kibanaDirectory}/src/legacy/core_plugins/$1/public$2`, '^legacy/plugins/xpack_main/(.*);': `${xPackKibanaDirectory}/legacy/plugins/xpack_main/public/$1`, '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': fileMockPath, diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionOverview/__jest__/TransactionOverview.test.tsx b/x-pack/legacy/plugins/apm/public/components/app/TransactionOverview/__jest__/TransactionOverview.test.tsx index a5356be72f5e..91e0ae11a652 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/TransactionOverview/__jest__/TransactionOverview.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/TransactionOverview/__jest__/TransactionOverview.test.tsx @@ -18,6 +18,7 @@ import { history } from '../../../../utils/history'; import { TransactionOverview } from '..'; import { IUrlParams } from '../../../../context/UrlParamsContext/types'; import * as useServiceTransactionTypesHook from '../../../../hooks/useServiceTransactionTypes'; +import * as useFetcherHook from '../../../../hooks/useFetcher'; import { fromQuery } from '../../../shared/Links/url_helpers'; import { Router } from 'react-router-dom'; import { UrlParamsProvider } from '../../../../context/UrlParamsContext'; @@ -51,6 +52,8 @@ function setup({ .spyOn(useServiceTransactionTypesHook, 'useServiceTransactionTypes') .mockReturnValue(serviceTransactionTypes); + jest.spyOn(useFetcherHook, 'useFetcher').mockReturnValue({} as any); + return render( diff --git a/x-pack/legacy/plugins/apm/public/components/shared/DatePicker/__test__/DatePicker.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/DatePicker/__test__/DatePicker.test.tsx index 05094c59712a..32379325c402 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/DatePicker/__test__/DatePicker.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/DatePicker/__test__/DatePicker.test.tsx @@ -10,13 +10,13 @@ import { UrlParamsContext, useUiFilters } from '../../../../context/UrlParamsContext'; -import { tick } from '../../../../utils/testHelpers'; import { DatePicker } from '../index'; import { IUrlParams } from '../../../../context/UrlParamsContext/types'; import { history } from '../../../../utils/history'; import { mount } from 'enzyme'; import { EuiSuperDatePicker } from '@elastic/eui'; import { MemoryRouter } from 'react-router-dom'; +import { wait } from '@testing-library/react'; const mockHistoryPush = jest.spyOn(history, 'push'); const mockRefreshTimeRange = jest.fn(); @@ -84,7 +84,7 @@ describe('DatePicker', () => { }); expect(mockRefreshTimeRange).not.toHaveBeenCalled(); jest.advanceTimersByTime(1000); - await tick(); + await wait(); expect(mockRefreshTimeRange).toHaveBeenCalled(); wrapper.unmount(); }); @@ -94,7 +94,7 @@ describe('DatePicker', () => { mountDatePicker({ refreshPaused: true, refreshInterval: 1000 }); expect(mockRefreshTimeRange).not.toHaveBeenCalled(); jest.advanceTimersByTime(1000); - await tick(); + await wait(); expect(mockRefreshTimeRange).not.toHaveBeenCalled(); }); }); diff --git a/x-pack/legacy/plugins/apm/public/components/shared/useDelayedVisibility/Delayed/index.ts b/x-pack/legacy/plugins/apm/public/components/shared/useDelayedVisibility/Delayed/index.ts index 798e872dbc47..9048afe57153 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/useDelayedVisibility/Delayed/index.ts +++ b/x-pack/legacy/plugins/apm/public/components/shared/useDelayedVisibility/Delayed/index.ts @@ -57,4 +57,10 @@ export class Delayed { public onChange(onChangeCallback: Callback) { this.onChangeCallback = onChangeCallback; } + + public destroy() { + if (this.timeoutId) { + window.clearTimeout(this.timeoutId); + } + } } diff --git a/x-pack/legacy/plugins/apm/public/components/shared/useDelayedVisibility/index.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/useDelayedVisibility/index.test.tsx index 57e634df2283..c55c6ab35184 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/useDelayedVisibility/index.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/useDelayedVisibility/index.test.tsx @@ -4,11 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ -import { renderHook } from '@testing-library/react-hooks'; +import { + renderHook, + act, + RenderHookResult +} from '@testing-library/react-hooks'; import { useDelayedVisibility } from '.'; describe('useFetcher', () => { - let hook; + let hook: RenderHookResult; + beforeEach(() => { jest.useFakeTimers(); }); @@ -26,9 +31,15 @@ describe('useFetcher', () => { }); hook.rerender(true); - jest.advanceTimersByTime(10); + act(() => { + jest.advanceTimersByTime(10); + }); + expect(hook.result.current).toEqual(false); - jest.advanceTimersByTime(50); + act(() => { + jest.advanceTimersByTime(50); + }); + expect(hook.result.current).toEqual(true); }); @@ -38,8 +49,11 @@ describe('useFetcher', () => { }); hook.rerender(true); - jest.advanceTimersByTime(100); + act(() => { + jest.advanceTimersByTime(100); + }); hook.rerender(false); + expect(hook.result.current).toEqual(true); }); @@ -49,11 +63,22 @@ describe('useFetcher', () => { }); hook.rerender(true); - jest.advanceTimersByTime(100); + + act(() => { + jest.advanceTimersByTime(100); + }); + hook.rerender(false); - jest.advanceTimersByTime(900); + act(() => { + jest.advanceTimersByTime(900); + }); + expect(hook.result.current).toEqual(true); - jest.advanceTimersByTime(100); + + act(() => { + jest.advanceTimersByTime(100); + }); + expect(hook.result.current).toEqual(false); }); }); diff --git a/x-pack/legacy/plugins/apm/public/components/shared/useDelayedVisibility/index.ts b/x-pack/legacy/plugins/apm/public/components/shared/useDelayedVisibility/index.ts index 5acbbd1d4573..c4465c7b4233 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/useDelayedVisibility/index.ts +++ b/x-pack/legacy/plugins/apm/public/components/shared/useDelayedVisibility/index.ts @@ -26,6 +26,10 @@ export function useDelayedVisibility( setIsVisible(visibility); }); delayedRef.current = delayed; + + return () => { + delayed.destroy(); + }; }, [hideDelayMs, showDelayMs, minimumVisibleDuration]); useEffect(() => { diff --git a/x-pack/legacy/plugins/apm/public/context/UrlParamsContext/__tests__/UrlParamsContext.test.tsx b/x-pack/legacy/plugins/apm/public/context/UrlParamsContext/__tests__/UrlParamsContext.test.tsx index 2604a3a12257..d2d8036e864a 100644 --- a/x-pack/legacy/plugins/apm/public/context/UrlParamsContext/__tests__/UrlParamsContext.test.tsx +++ b/x-pack/legacy/plugins/apm/public/context/UrlParamsContext/__tests__/UrlParamsContext.test.tsx @@ -11,8 +11,8 @@ import { Location, History } from 'history'; import { MemoryRouter, Router } from 'react-router-dom'; import moment from 'moment-timezone'; import { IUrlParams } from '../types'; -import { tick } from '../../../utils/testHelpers'; import { getParsedDate } from '../helpers'; +import { wait } from '@testing-library/react'; function mountParams(location: Location) { return mount( @@ -143,13 +143,13 @@ describe('UrlParamsContext', () => { ); - await tick(); + await wait(); expect(calls.length).toBe(1); wrapper.find('button').simulate('click'); - await tick(); + await wait(); expect(calls.length).toBe(2); @@ -194,11 +194,11 @@ describe('UrlParamsContext', () => { ); - await tick(); + await wait(); wrapper.find('button').simulate('click'); - await tick(); + await wait(); const params = getDataFromOutput(wrapper); expect(params.start).toEqual('2000-06-14T00:00:00.000Z'); diff --git a/x-pack/legacy/plugins/apm/public/hooks/useFetcher.integration.test.tsx b/x-pack/legacy/plugins/apm/public/hooks/useFetcher.integration.test.tsx index 36a8377c0252..743cf4e01e55 100644 --- a/x-pack/legacy/plugins/apm/public/hooks/useFetcher.integration.test.tsx +++ b/x-pack/legacy/plugins/apm/public/hooks/useFetcher.integration.test.tsx @@ -5,8 +5,8 @@ */ import React from 'react'; -import { render } from '@testing-library/react'; -import { delay, tick } from '../utils/testHelpers'; +import { render, wait } from '@testing-library/react'; +import { delay } from '../utils/testHelpers'; import { useFetcher } from './useFetcher'; import { KibanaCoreContext } from '../../../observability/public/context/kibana_core'; import { LegacyCoreStart } from 'kibana/public'; @@ -76,7 +76,8 @@ describe('when simulating race condition', () => { it('should render "Hello from Peter" after 200ms', async () => { jest.advanceTimersByTime(200); - await tick(); + + await wait(); expect(renderSpy).lastCalledWith({ data: 'Hello from Peter', @@ -87,7 +88,7 @@ describe('when simulating race condition', () => { it('should render "Hello from Peter" after 600ms', async () => { jest.advanceTimersByTime(600); - await tick(); + await wait(); expect(renderSpy).lastCalledWith({ data: 'Hello from Peter', @@ -98,7 +99,7 @@ describe('when simulating race condition', () => { it('should should NOT have rendered "Hello from John" at any point', async () => { jest.advanceTimersByTime(600); - await tick(); + await wait(); expect(renderSpy).not.toHaveBeenCalledWith({ data: 'Hello from John', @@ -109,7 +110,7 @@ describe('when simulating race condition', () => { it('should send and receive calls in the right order', async () => { jest.advanceTimersByTime(600); - await tick(); + await wait(); expect(requestCallOrder).toEqual([ ['request', 'John', 500], diff --git a/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx b/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx index b5cee4a78b01..9e3c486715a1 100644 --- a/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx +++ b/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx @@ -58,7 +58,6 @@ export async function getRenderedHref(Component: React.FC, location: Location) { ); - await tick(); await waitForElement(() => el.container.querySelector('a')); const a = el.container.querySelector('a'); @@ -74,9 +73,6 @@ export function delay(ms: number) { return new Promise(resolve => setTimeout(resolve, ms)); } -// Await this when you need to "flush" promises to immediately resolve or throw in tests -export const tick = () => new Promise(resolve => setImmediate(resolve, 0)); - export function expectTextsNotInDocument(output: any, texts: string[]) { texts.forEach(text => { try { diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/embeddable.tsx b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/embeddable.tsx index 8810871e9161..5c7ef1a8c179 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/embeddable.tsx +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/embeddable.tsx @@ -16,8 +16,11 @@ import { } from '../../../../../../src/legacy/core_plugins/embeddable_api/public/np_ready/public'; import { start } from '../../../../../../src/legacy/core_plugins/embeddable_api/public/np_ready/public/legacy'; import { EmbeddableExpression } from '../expression_types/embeddable'; -import { SavedObjectFinder } from '../../../../../../src/legacy/ui/public/saved_objects/components/saved_object_finder'; import { RendererStrings } from '../../i18n'; +import { + SavedObjectFinderProps, + SavedObjectFinderUi, +} from '../../../../../../src/plugins/kibana_react/public'; const { embeddable: strings } = RendererStrings; @@ -34,6 +37,13 @@ interface Handlers { } const renderEmbeddable = (embeddableObject: IEmbeddable, domNode: HTMLElement) => { + const SavedObjectFinder = (props: SavedObjectFinderProps) => ( + + ); return (
{ - > { const { help, args: argHelp } = getFunctionHelp().timelion; @@ -64,8 +87,8 @@ export function timelion(): ExpressionFunction<'timelion', Filter, Arguments, Pr // workpad, if it exists. Otherwise fall back on the function args. const timeFilter = context.and.find(and => and.type === 'time'); const range = timeFilter - ? { from: timeFilter.from, to: timeFilter.to } - : { from: args.from, to: args.to }; + ? { min: timeFilter.from, max: timeFilter.to } + : parseDateMath({ from: args.from, to: args.to }, args.timezone); const body = { extended: { @@ -79,8 +102,8 @@ export function timelion(): ExpressionFunction<'timelion', Filter, Arguments, Pr }, sheet: [args.query], time: { - from: range.from, - to: range.to, + from: range.min, + to: range.max, interval: args.interval, timezone: args.timezone, }, diff --git a/x-pack/legacy/plugins/graph/public/components/source_picker.tsx b/x-pack/legacy/plugins/graph/public/components/source_picker.tsx index 2399ac24c199..de920af8a48a 100644 --- a/x-pack/legacy/plugins/graph/public/components/source_picker.tsx +++ b/x-pack/legacy/plugins/graph/public/components/source_picker.tsx @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; import { CoreStart } from 'src/core/public'; -import { SavedObjectFinder } from '../../../../../../src/plugins/kibana_react/public'; +import { SavedObjectFinderUi } from '../../../../../../src/plugins/kibana_react/public'; import { IndexPatternSavedObject } from '../types'; export interface SourcePickerProps { @@ -25,7 +25,7 @@ export function SourcePicker({ onIndexPatternSelected, }: SourcePickerProps) { return ( - { diff --git a/x-pack/legacy/plugins/infra/common/http_api/log_analysis/results/log_entry_rate.ts b/x-pack/legacy/plugins/infra/common/http_api/log_analysis/results/log_entry_rate.ts index 5a1412fd8f3d..dfc3d2aabd11 100644 --- a/x-pack/legacy/plugins/infra/common/http_api/log_analysis/results/log_entry_rate.ts +++ b/x-pack/legacy/plugins/infra/common/http_api/log_analysis/results/log_entry_rate.ts @@ -29,7 +29,7 @@ export type GetLogEntryRateRequestPayload = rt.TypeOf; + +export const logEntryRateHistogramBucketRT = rt.type({ partitions: rt.array(logEntryRatePartitionRT), startTime: rt.number, }); +export type LogEntryRateHistogramBucket = rt.TypeOf; + export const getLogEntryRateSuccessReponsePayloadRT = rt.type({ data: rt.type({ bucketDuration: rt.number, - histogramBuckets: rt.array(logEntryRateHistogramBucket), + histogramBuckets: rt.array(logEntryRateHistogramBucketRT), totalNumberOfLogEntries: rt.number, }), }); diff --git a/x-pack/legacy/plugins/infra/common/http_api/log_analysis/validation/index.ts b/x-pack/legacy/plugins/infra/common/http_api/log_analysis/validation/index.ts index 727faca69298..f23ef7ee7c30 100644 --- a/x-pack/legacy/plugins/infra/common/http_api/log_analysis/validation/index.ts +++ b/x-pack/legacy/plugins/infra/common/http_api/log_analysis/validation/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export * from './indices'; +export * from './log_entry_rate_indices'; diff --git a/x-pack/legacy/plugins/infra/common/http_api/log_analysis/validation/indices.ts b/x-pack/legacy/plugins/infra/common/http_api/log_analysis/validation/log_entry_rate_indices.ts similarity index 74% rename from x-pack/legacy/plugins/infra/common/http_api/log_analysis/validation/indices.ts rename to x-pack/legacy/plugins/infra/common/http_api/log_analysis/validation/log_entry_rate_indices.ts index 62d81dc13685..5b2509074f6e 100644 --- a/x-pack/legacy/plugins/infra/common/http_api/log_analysis/validation/indices.ts +++ b/x-pack/legacy/plugins/infra/common/http_api/log_analysis/validation/log_entry_rate_indices.ts @@ -6,14 +6,24 @@ import * as rt from 'io-ts'; -export const LOG_ANALYSIS_VALIDATION_INDICES_PATH = '/api/infra/log_analysis/validation/indices'; +export const LOG_ANALYSIS_VALIDATE_INDICES_PATH = + '/api/infra/log_analysis/validation/log_entry_rate_indices'; /** * Request types */ +export const validationIndicesFieldSpecificationRT = rt.type({ + name: rt.string, + validTypes: rt.array(rt.string), +}); + +export type ValidationIndicesFieldSpecification = rt.TypeOf< + typeof validationIndicesFieldSpecificationRT +>; + export const validationIndicesRequestPayloadRT = rt.type({ data: rt.type({ - timestampField: rt.string, + fields: rt.array(validationIndicesFieldSpecificationRT), indices: rt.array(rt.string), }), }); diff --git a/x-pack/legacy/plugins/infra/common/log_analysis/job_parameters.ts b/x-pack/legacy/plugins/infra/common/log_analysis/job_parameters.ts index 5cfe38394a2c..626e90b65a7d 100644 --- a/x-pack/legacy/plugins/infra/common/log_analysis/job_parameters.ts +++ b/x-pack/legacy/plugins/infra/common/log_analysis/job_parameters.ts @@ -4,19 +4,25 @@ * you may not use this file except in compliance with the Elastic License. */ -import { JobType } from './log_analysis'; +import * as rt from 'io-ts'; export const bucketSpan = 900000; +export const partitionField = 'event.dataset'; + export const getJobIdPrefix = (spaceId: string, sourceId: string) => `kibana-logs-ui-${spaceId}-${sourceId}-`; -export const getJobId = (spaceId: string, sourceId: string, jobType: JobType) => +export const getJobId = (spaceId: string, sourceId: string, jobType: string) => `${getJobIdPrefix(spaceId, sourceId)}${jobType}`; -export const getDatafeedId = (spaceId: string, sourceId: string, jobType: JobType) => +export const getDatafeedId = (spaceId: string, sourceId: string, jobType: string) => `datafeed-${getJobId(spaceId, sourceId, jobType)}`; -export const getAllModuleJobIds = (spaceId: string, sourceId: string) => [ - getJobId(spaceId, sourceId, 'log-entry-rate'), -]; +export const jobSourceConfigurationRT = rt.type({ + indexPattern: rt.string, + timestampField: rt.string, + bucketSpan: rt.number, +}); + +export type JobSourceConfiguration = rt.TypeOf; diff --git a/x-pack/legacy/plugins/infra/public/components/formatted_time.tsx b/x-pack/legacy/plugins/infra/public/components/formatted_time.tsx index 78255c55df12..46b505d4fab5 100644 --- a/x-pack/legacy/plugins/infra/public/components/formatted_time.tsx +++ b/x-pack/legacy/plugins/infra/public/components/formatted_time.tsx @@ -37,7 +37,6 @@ export const useFormattedTime = ( const dateFormat = formatMap[format]; const formattedTime = useMemo(() => getFormattedTime(time, dateFormat, fallbackFormat), [ - getFormattedTime, time, dateFormat, fallbackFormat, diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/analyze_in_ml_button.tsx b/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_results/analyze_in_ml_button.tsx similarity index 96% rename from x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/analyze_in_ml_button.tsx rename to x-pack/legacy/plugins/infra/public/components/logging/log_analysis_results/analyze_in_ml_button.tsx index ef81f229034b..c5d83e1c205c 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/analyze_in_ml_button.tsx +++ b/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_results/analyze_in_ml_button.tsx @@ -4,14 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; -import url from 'url'; import { EuiButton } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; +import React from 'react'; +import { encode } from 'rison-node'; import chrome from 'ui/chrome'; import { QueryString } from 'ui/utils/query_string'; -import { encode } from 'rison-node'; -import { TimeRange } from '../../../../../common/http_api/shared/time_range'; +import url from 'url'; + +import { TimeRange } from '../../../../common/http_api/shared/time_range'; export const AnalyzeInMlButton: React.FunctionComponent<{ jobId: string; diff --git a/x-pack/legacy/plugins/watcher/public/lib/documentation_links/index.ts b/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_results/index.ts similarity index 85% rename from x-pack/legacy/plugins/watcher/public/lib/documentation_links/index.ts rename to x-pack/legacy/plugins/infra/public/components/logging/log_analysis_results/index.ts index 81e0c494e28b..8a4ceb70252a 100644 --- a/x-pack/legacy/plugins/watcher/public/lib/documentation_links/index.ts +++ b/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_results/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export * from './documentation_links'; +export * from './analyze_in_ml_button'; diff --git a/x-pack/legacy/plugins/infra/public/components/logging/log_entry_flyout/log_entry_actions_menu.tsx b/x-pack/legacy/plugins/infra/public/components/logging/log_entry_flyout/log_entry_actions_menu.tsx index 92c6ddd19360..d018b3a0f38f 100644 --- a/x-pack/legacy/plugins/infra/public/components/logging/log_entry_flyout/log_entry_actions_menu.tsx +++ b/x-pack/legacy/plugins/infra/public/components/logging/log_entry_flyout/log_entry_actions_menu.tsx @@ -51,7 +51,7 @@ export const LogEntryActionsMenu: React.FunctionComponent<{ /> , ], - [uptimeLink] + [apmLink, uptimeLink] ); const hasMenuItems = useMemo(() => menuItems.length > 0, [menuItems]); diff --git a/x-pack/legacy/plugins/infra/public/components/logging/log_highlights_menu.tsx b/x-pack/legacy/plugins/infra/public/components/logging/log_highlights_menu.tsx index 24a5e8bacb4f..d13ccde7466c 100644 --- a/x-pack/legacy/plugins/infra/public/components/logging/log_highlights_menu.tsx +++ b/x-pack/legacy/plugins/infra/public/components/logging/log_highlights_menu.tsx @@ -16,7 +16,7 @@ import { import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { debounce } from 'lodash'; -import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import React, { useCallback, useMemo, useState } from 'react'; import euiStyled from '../../../../../common/eui_styled_components'; import { useVisibilityState } from '../../utils/use_visibility_state'; @@ -47,8 +47,25 @@ export const LogHighlightsMenu: React.FC = ({ } = useVisibilityState(false); // Input field state - const [highlightTerm, setHighlightTerm] = useState(''); + const [highlightTerm, _setHighlightTerm] = useState(''); + const debouncedOnChange = useMemo(() => debounce(onChange, 275), [onChange]); + const setHighlightTerm = useCallback( + valueOrUpdater => + _setHighlightTerm(previousHighlightTerm => { + const newHighlightTerm = + typeof valueOrUpdater === 'function' + ? valueOrUpdater(previousHighlightTerm) + : valueOrUpdater; + + if (newHighlightTerm !== previousHighlightTerm) { + debouncedOnChange([newHighlightTerm]); + } + + return newHighlightTerm; + }), + [debouncedOnChange] + ); const changeHighlightTerm = useCallback( e => { const value = e.target.value; @@ -57,9 +74,6 @@ export const LogHighlightsMenu: React.FC = ({ [setHighlightTerm] ); const clearHighlightTerm = useCallback(() => setHighlightTerm(''), [setHighlightTerm]); - useEffect(() => { - debouncedOnChange([highlightTerm]); - }, [highlightTerm]); const button = ( diff --git a/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/text_styles.tsx b/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/text_styles.tsx index 1d40c88f5d1d..e95ac6aa7923 100644 --- a/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/text_styles.tsx +++ b/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/text_styles.tsx @@ -63,7 +63,7 @@ export const useMeasuredCharacterDimensions = (scale: TextScale) => { X ), - [scale] + [measureElement, scale] ); return { diff --git a/x-pack/legacy/plugins/infra/public/components/metrics_explorer/metrics.tsx b/x-pack/legacy/plugins/infra/public/components/metrics_explorer/metrics.tsx index d59e709d9a19..42df7c6915a0 100644 --- a/x-pack/legacy/plugins/infra/public/components/metrics_explorer/metrics.tsx +++ b/x-pack/legacy/plugins/infra/public/components/metrics_explorer/metrics.tsx @@ -7,7 +7,7 @@ import { EuiComboBox } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import React, { useCallback, useState, useEffect } from 'react'; +import React, { useCallback, useState } from 'react'; import { FieldType } from 'ui/index_patterns'; import { colorTransformer, MetricsExplorerColor } from '../../../common/color_palette'; import { @@ -31,24 +31,19 @@ interface SelectedOption { export const MetricsExplorerMetrics = ({ options, onChange, fields, autoFocus = false }: Props) => { const colors = Object.keys(MetricsExplorerColor) as MetricsExplorerColor[]; - const [inputRef, setInputRef] = useState(null); - const [focusOnce, setFocusState] = useState(false); + const [shouldFocus, setShouldFocus] = useState(autoFocus); - useEffect(() => { - if (inputRef && autoFocus && !focusOnce) { - inputRef.focus(); - setFocusState(true); - } - }, [inputRef]); + // the EuiCombobox forwards the ref to an input element + const autoFocusInputElement = useCallback( + (inputElement: HTMLInputElement | null) => { + if (inputElement && shouldFocus) { + inputElement.focus(); + setShouldFocus(false); + } + }, + [shouldFocus] + ); - // I tried to use useRef originally but the EUIComboBox component's type definition - // would only accept an actual input element or a callback function (with the same type). - // This effectivly does the same thing but is compatible with EuiComboBox. - const handleInputRef = (ref: HTMLInputElement) => { - if (ref) { - setInputRef(ref); - } - }; const handleChange = useCallback( selectedOptions => { onChange( @@ -59,7 +54,7 @@ export const MetricsExplorerMetrics = ({ options, onChange, fields, autoFocus = })) ); }, - [options, onChange] + [onChange, options.aggregation, colors] ); const comboOptions = fields @@ -86,7 +81,7 @@ export const MetricsExplorerMetrics = ({ options, onChange, fields, autoFocus = selectedOptions={selectedOptions} onChange={handleChange} isClearable={true} - inputRef={handleInputRef} + inputRef={autoFocusInputElement} /> ); }; diff --git a/x-pack/legacy/plugins/infra/public/components/saved_views/create_modal.tsx b/x-pack/legacy/plugins/infra/public/components/saved_views/create_modal.tsx index 8df479f36e2f..9b8907a1ff9e 100644 --- a/x-pack/legacy/plugins/infra/public/components/saved_views/create_modal.tsx +++ b/x-pack/legacy/plugins/infra/public/components/saved_views/create_modal.tsx @@ -36,7 +36,7 @@ export const SavedViewCreateModal = ({ close, save, isInvalid }: Props) => { const saveView = useCallback(() => { save(viewName, includeTime); - }, [viewName, includeTime]); + }, [includeTime, save, viewName]); return ( diff --git a/x-pack/legacy/plugins/infra/public/components/source_configuration/add_log_column_popover.tsx b/x-pack/legacy/plugins/infra/public/components/source_configuration/add_log_column_popover.tsx index 9b83f62e7856..fc8407c5298e 100644 --- a/x-pack/legacy/plugins/infra/public/components/source_configuration/add_log_column_popover.tsx +++ b/x-pack/legacy/plugins/infra/public/components/source_configuration/add_log_column_popover.tsx @@ -94,7 +94,7 @@ export const AddLogColumnButtonAndPopover: React.FunctionComponent<{ addLogColumn(selectedOption.columnConfiguration); }, - [addLogColumn, availableColumnOptions] + [addLogColumn, availableColumnOptions, closePopover] ); return ( diff --git a/x-pack/legacy/plugins/infra/public/components/source_configuration/fields_configuration_panel.tsx b/x-pack/legacy/plugins/infra/public/components/source_configuration/fields_configuration_panel.tsx index 771285e8ccee..5f3d1a63e72e 100644 --- a/x-pack/legacy/plugins/infra/public/components/source_configuration/fields_configuration_panel.tsx +++ b/x-pack/legacy/plugins/infra/public/components/source_configuration/fields_configuration_panel.tsx @@ -50,10 +50,12 @@ export const FieldsConfigurationPanel = ({ +

+ +

} description={ +

+ +

} description={ +

+ +

} description={ +

+ +

} description={ +

+ +

} description={ +

+ +

} description={ +

+ +

} description={ +

+ +

} description={ { indicesConfigurationFormState.resetForm(); logColumnsConfigurationFormState.resetForm(); - }, [indicesConfigurationFormState.resetForm, logColumnsConfigurationFormState.formState]); + }, [indicesConfigurationFormState, logColumnsConfigurationFormState]); const isFormDirty = useMemo( () => indicesConfigurationFormState.isFormDirty || logColumnsConfigurationFormState.isFormDirty, diff --git a/x-pack/legacy/plugins/infra/public/components/waffle/waffle_inventory_switcher.tsx b/x-pack/legacy/plugins/infra/public/components/waffle/waffle_inventory_switcher.tsx index 38e87038b7c4..c8f03cef4d6a 100644 --- a/x-pack/legacy/plugins/infra/public/components/waffle/waffle_inventory_switcher.tsx +++ b/x-pack/legacy/plugins/infra/public/components/waffle/waffle_inventory_switcher.tsx @@ -17,28 +17,33 @@ import { } from '../../graphql/types'; import { findInventoryModel } from '../../../common/inventory_models'; -interface Props { +interface WaffleInventorySwitcherProps { nodeType: InfraNodeType; changeNodeType: (nodeType: InfraNodeType) => void; changeGroupBy: (groupBy: InfraSnapshotGroupbyInput[]) => void; changeMetric: (metric: InfraSnapshotMetricInput) => void; } -export const WaffleInventorySwitcher = (props: Props) => { +export const WaffleInventorySwitcher: React.FC = ({ + changeNodeType, + changeGroupBy, + changeMetric, + nodeType, +}) => { const [isOpen, setIsOpen] = useState(false); const closePopover = useCallback(() => setIsOpen(false), []); const openPopover = useCallback(() => setIsOpen(true), []); const goToNodeType = useCallback( - (nodeType: InfraNodeType) => { + (targetNodeType: InfraNodeType) => { closePopover(); - props.changeNodeType(nodeType); - props.changeGroupBy([]); - const inventoryModel = findInventoryModel(nodeType); - props.changeMetric({ + changeNodeType(targetNodeType); + changeGroupBy([]); + const inventoryModel = findInventoryModel(targetNodeType); + changeMetric({ type: inventoryModel.metrics.defaultSnapshot as InfraSnapshotMetricType, }); }, - [props.changeGroupBy, props.changeNodeType, props.changeMetric] + [closePopover, changeNodeType, changeGroupBy, changeMetric] ); const goToHost = useCallback(() => goToNodeType('host' as InfraNodeType), [goToNodeType]); const goToK8 = useCallback(() => goToNodeType('pod' as InfraNodeType), [goToNodeType]); @@ -68,10 +73,10 @@ export const WaffleInventorySwitcher = (props: Props) => { ], }, ], - [] + [goToDocker, goToHost, goToK8] ); const selectedText = useMemo(() => { - switch (props.nodeType) { + switch (nodeType) { case InfraNodeType.host: return i18n.translate('xpack.infra.waffle.nodeTypeSwitcher.hostsLabel', { defaultMessage: 'Hosts', @@ -81,7 +86,7 @@ export const WaffleInventorySwitcher = (props: Props) => { case InfraNodeType.container: return 'Docker'; } - }, [props.nodeType]); + }, [nodeType]); return ( diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_api_types.ts b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_api_types.ts index deb3d528e42c..9d4d419ceebe 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_api_types.ts +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_api_types.ts @@ -6,11 +6,30 @@ import * as rt from 'io-ts'; +import { jobSourceConfigurationRT } from '../../../../../common/log_analysis'; + export const jobCustomSettingsRT = rt.partial({ job_revision: rt.number, - logs_source_config: rt.partial({ - indexPattern: rt.string, - timestampField: rt.string, - bucketSpan: rt.number, + logs_source_config: rt.partial(jobSourceConfigurationRT.props), +}); + +export const getMlCapabilitiesResponsePayloadRT = rt.type({ + capabilities: rt.type({ + canGetJobs: rt.boolean, + canCreateJob: rt.boolean, + canDeleteJob: rt.boolean, + canOpenJob: rt.boolean, + canCloseJob: rt.boolean, + canForecastJob: rt.boolean, + canGetDatafeeds: rt.boolean, + canStartStopDatafeed: rt.boolean, + canUpdateJob: rt.boolean, + canUpdateDatafeed: rt.boolean, + canPreviewDatafeed: rt.boolean, }), + isPlatinumOrTrialLicense: rt.boolean, + mlFeatureEnabledInSpace: rt.boolean, + upgradeInProgress: rt.boolean, }); + +export type GetMlCapabilitiesResponsePayload = rt.TypeOf; diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_cleanup.ts b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_cleanup.ts index 209da920c4c8..5054f607fa5d 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_cleanup.ts +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_cleanup.ts @@ -9,17 +9,22 @@ import { pipe } from 'fp-ts/lib/pipeable'; import { fold } from 'fp-ts/lib/Either'; import { identity } from 'fp-ts/lib/function'; import { kfetch } from 'ui/kfetch'; -import { getAllModuleJobIds, getDatafeedId } from '../../../../../common/log_analysis'; + +import { getDatafeedId, getJobId } from '../../../../../common/log_analysis'; import { throwErrors, createPlainError } from '../../../../../common/runtime_types'; -export const callDeleteJobs = async (spaceId: string, sourceId: string) => { +export const callDeleteJobs = async ( + spaceId: string, + sourceId: string, + jobTypes: JobType[] +) => { // NOTE: Deleting the jobs via this API will delete the datafeeds at the same time const deleteJobsResponse = await kfetch({ method: 'POST', pathname: '/api/ml/jobs/delete_jobs', body: JSON.stringify( deleteJobsRequestPayloadRT.encode({ - jobIds: getAllModuleJobIds(spaceId, sourceId), + jobIds: jobTypes.map(jobType => getJobId(spaceId, sourceId, jobType)), }) ), }); @@ -42,15 +47,24 @@ export const callGetJobDeletionTasks = async () => { ); }; -export const callStopDatafeed = async (spaceId: string, sourceId: string) => { +export const callStopDatafeeds = async ( + spaceId: string, + sourceId: string, + jobTypes: JobType[] +) => { // Stop datafeed due to https://github.com/elastic/kibana/issues/44652 const stopDatafeedResponse = await kfetch({ method: 'POST', - pathname: `/api/ml/datafeeds/${getDatafeedId(spaceId, sourceId, 'log-entry-rate')}/_stop`, + pathname: '/api/ml/jobs/stop_datafeeds', + body: JSON.stringify( + stopDatafeedsRequestPayloadRT.encode({ + datafeedIds: jobTypes.map(jobType => getDatafeedId(spaceId, sourceId, jobType)), + }) + ), }); return pipe( - stopDatafeedResponsePayloadRT.decode(stopDatafeedResponse), + stopDatafeedsResponsePayloadRT.decode(stopDatafeedResponse), fold(throwErrors(createPlainError), identity) ); }; @@ -68,10 +82,19 @@ export const deleteJobsResponsePayloadRT = rt.record( }) ); +export type DeleteJobsResponsePayload = rt.TypeOf; + export const getJobDeletionTasksResponsePayloadRT = rt.type({ jobIds: rt.array(rt.string), }); -export const stopDatafeedResponsePayloadRT = rt.type({ - stopped: rt.boolean, +export const stopDatafeedsRequestPayloadRT = rt.type({ + datafeedIds: rt.array(rt.string), }); + +export const stopDatafeedsResponsePayloadRT = rt.record( + rt.string, + rt.type({ + stopped: rt.boolean, + }) +); diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_get_jobs_summary_api.ts b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_get_jobs_summary_api.ts index 6171d10b5f1a..91e517b0db00 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_get_jobs_summary_api.ts +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_get_jobs_summary_api.ts @@ -12,15 +12,19 @@ import { kfetch } from 'ui/kfetch'; import { jobCustomSettingsRT } from './ml_api_types'; import { throwErrors, createPlainError } from '../../../../../common/runtime_types'; -import { getAllModuleJobIds } from '../../../../../common/log_analysis'; +import { getJobId } from '../../../../../common/log_analysis'; -export const callJobsSummaryAPI = async (spaceId: string, sourceId: string) => { +export const callJobsSummaryAPI = async ( + spaceId: string, + sourceId: string, + jobTypes: JobType[] +) => { const response = await kfetch({ method: 'POST', pathname: '/api/ml/jobs/jobs_summary', body: JSON.stringify( fetchJobStatusRequestPayloadRT.encode({ - jobIds: getAllModuleJobIds(spaceId, sourceId), + jobIds: jobTypes.map(jobType => getJobId(spaceId, sourceId, jobType)), }) ), }); diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_setup_module_api.ts b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_setup_module_api.ts index 1c937513c795..80a4f975cdd5 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_setup_module_api.ts +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_setup_module_api.ts @@ -12,7 +12,6 @@ import { kfetch } from 'ui/kfetch'; import { throwErrors, createPlainError } from '../../../../../common/runtime_types'; import { getJobIdPrefix } from '../../../../../common/log_analysis'; -import { jobCustomSettingsRT } from './ml_api_types'; export const callSetupMlModuleAPI = async ( moduleId: string, @@ -21,8 +20,8 @@ export const callSetupMlModuleAPI = async ( spaceId: string, sourceId: string, indexPattern: string, - timeField: string, - bucketSpan: number + jobOverrides: SetupMlModuleJobOverrides[] = [], + datafeedOverrides: SetupMlModuleDatafeedOverrides[] = [] ) => { const response = await kfetch({ method: 'POST', @@ -34,25 +33,8 @@ export const callSetupMlModuleAPI = async ( indexPatternName: indexPattern, prefix: getJobIdPrefix(spaceId, sourceId), startDatafeed: true, - jobOverrides: [ - { - job_id: 'log-entry-rate' as const, - analysis_config: { - bucket_span: `${bucketSpan}ms`, - }, - data_description: { - time_field: timeField, - }, - custom_settings: { - logs_source_config: { - indexPattern, - timestampField: timeField, - bucketSpan, - }, - }, - }, - ], - datafeedOverrides: [], + jobOverrides, + datafeedOverrides, }) ), }); @@ -68,23 +50,20 @@ const setupMlModuleTimeParamsRT = rt.partial({ end: rt.number, }); -const setupMlModuleLogEntryRateJobOverridesRT = rt.type({ - job_id: rt.literal('log-entry-rate'), - analysis_config: rt.type({ - bucket_span: rt.string, - }), - data_description: rt.type({ - time_field: rt.string, - }), - custom_settings: jobCustomSettingsRT, -}); +const setupMlModuleJobOverridesRT = rt.object; + +export type SetupMlModuleJobOverrides = rt.TypeOf; + +const setupMlModuleDatafeedOverridesRT = rt.object; + +export type SetupMlModuleDatafeedOverrides = rt.TypeOf; const setupMlModuleRequestParamsRT = rt.type({ indexPatternName: rt.string, prefix: rt.string, startDatafeed: rt.boolean, - jobOverrides: rt.array(setupMlModuleLogEntryRateJobOverridesRT), - datafeedOverrides: rt.array(rt.object), + jobOverrides: rt.array(setupMlModuleJobOverridesRT), + datafeedOverrides: rt.array(setupMlModuleDatafeedOverridesRT), }); const setupMlModuleRequestPayloadRT = rt.intersection([ diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/index_patterns_validate.ts b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/validate_indices.ts similarity index 70% rename from x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/index_patterns_validate.ts rename to x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/validate_indices.ts index 440ee10e4223..0d2e9b673488 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/index_patterns_validate.ts +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/validate_indices.ts @@ -10,20 +10,22 @@ import { identity } from 'fp-ts/lib/function'; import { kfetch } from 'ui/kfetch'; import { - LOG_ANALYSIS_VALIDATION_INDICES_PATH, + LOG_ANALYSIS_VALIDATE_INDICES_PATH, + ValidationIndicesFieldSpecification, validationIndicesRequestPayloadRT, validationIndicesResponsePayloadRT, } from '../../../../../common/http_api'; import { throwErrors, createPlainError } from '../../../../../common/runtime_types'; -export const callIndexPatternsValidate = async (timestampField: string, indices: string[]) => { +export const callValidateIndicesAPI = async ( + indices: string[], + fields: ValidationIndicesFieldSpecification[] +) => { const response = await kfetch({ method: 'POST', - pathname: LOG_ANALYSIS_VALIDATION_INDICES_PATH, - body: JSON.stringify( - validationIndicesRequestPayloadRT.encode({ data: { timestampField, indices } }) - ), + pathname: LOG_ANALYSIS_VALIDATE_INDICES_PATH, + body: JSON.stringify(validationIndicesRequestPayloadRT.encode({ data: { indices, fields } })), }); return pipe( diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/index.ts b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/index.ts index cbe3b2ef1e9b..eb044c86e50f 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/index.ts +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/index.ts @@ -6,7 +6,6 @@ export * from './log_analysis_capabilities'; export * from './log_analysis_cleanup'; -export * from './log_analysis_jobs'; -export * from './log_analysis_results'; -export * from './log_analysis_results_url_state'; -export * from './log_analysis_status_state'; +export * from './log_analysis_module'; +export * from './log_analysis_module_status'; +export * from './log_analysis_module_types'; diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_capabilities.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_capabilities.tsx index 7ac7d051e678..bb01043b0db6 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_capabilities.tsx +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_capabilities.tsx @@ -15,7 +15,7 @@ import { useTrackedPromise } from '../../../utils/use_tracked_promise'; import { getMlCapabilitiesResponsePayloadRT, GetMlCapabilitiesResponsePayload, -} from './ml_api_types'; +} from './api/ml_api_types'; import { throwErrors, createPlainError } from '../../../../common/runtime_types'; export const useLogAnalysisCapabilities = () => { @@ -46,7 +46,7 @@ export const useLogAnalysisCapabilities = () => { useEffect(() => { fetchMlCapabilities(); - }, []); + }, [fetchMlCapabilities]); const isLoading = useMemo(() => fetchMlCapabilitiesRequest.state === 'pending', [ fetchMlCapabilitiesRequest.state, diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_cleanup.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_cleanup.tsx index 1b79d3c1ef78..a37d18cc33cf 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_cleanup.tsx +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_cleanup.tsx @@ -4,64 +4,46 @@ * you may not use this file except in compliance with the Elastic License. */ -import createContainer from 'constate'; -import { useMemo } from 'react'; -import { useTrackedPromise } from '../../../utils/use_tracked_promise'; -import { callDeleteJobs, callStopDatafeed, callGetJobDeletionTasks } from './api/ml_cleanup'; -import { getAllModuleJobIds } from '../../../../common/log_analysis'; - -export const useLogAnalysisCleanup = ({ - sourceId, - spaceId, -}: { - sourceId: string; - spaceId: string; -}) => { - const [cleanupMLResourcesRequest, cleanupMLResources] = useTrackedPromise( - { - cancelPreviousOn: 'resolution', - createPromise: async () => { - try { - await callStopDatafeed(spaceId, sourceId); - } catch (err) { - // Datefeed has been deleted / doesn't exist, proceed with deleting jobs anyway - if (err && err.res && err.res.status === 404) { - return await deleteJobs(spaceId, sourceId); - } else { - throw err; - } - } - - return await deleteJobs(spaceId, sourceId); - }, - }, - [spaceId, sourceId] - ); - - const isCleaningUp = useMemo(() => cleanupMLResourcesRequest.state === 'pending', [ - cleanupMLResourcesRequest.state, - ]); +import { getJobId } from '../../../../common/log_analysis'; +import { callDeleteJobs, callGetJobDeletionTasks, callStopDatafeeds } from './api/ml_cleanup'; + +export const cleanUpJobsAndDatafeeds = async ( + spaceId: string, + sourceId: string, + jobTypes: JobType[] +) => { + try { + await callStopDatafeeds(spaceId, sourceId, jobTypes); + } catch (err) { + // Proceed only if datafeed has been deleted or didn't exist in the first place + if (err?.res?.status !== 404) { + throw err; + } + } - return { - cleanupMLResources, - isCleaningUp, - }; + return await deleteJobs(spaceId, sourceId, jobTypes); }; -export const LogAnalysisCleanup = createContainer(useLogAnalysisCleanup); - -const deleteJobs = async (spaceId: string, sourceId: string) => { - const deleteJobsResponse = await callDeleteJobs(spaceId, sourceId); - await waitUntilJobsAreDeleted(spaceId, sourceId); +const deleteJobs = async ( + spaceId: string, + sourceId: string, + jobTypes: JobType[] +) => { + const deleteJobsResponse = await callDeleteJobs(spaceId, sourceId, jobTypes); + await waitUntilJobsAreDeleted(spaceId, sourceId, jobTypes); return deleteJobsResponse; }; -const waitUntilJobsAreDeleted = async (spaceId: string, sourceId: string) => { +const waitUntilJobsAreDeleted = async ( + spaceId: string, + sourceId: string, + jobTypes: JobType[] +) => { + const moduleJobIds = jobTypes.map(jobType => getJobId(spaceId, sourceId, jobType)); while (true) { - const response = await callGetJobDeletionTasks(); - const jobIdsBeingDeleted = response.jobIds; - const moduleJobIds = getAllModuleJobIds(spaceId, sourceId); + const { jobIds: jobIdsBeingDeleted } = await callGetJobDeletionTasks(); const needToWait = jobIdsBeingDeleted.some(jobId => moduleJobIds.includes(jobId)); + if (needToWait) { await timeout(1000); } else { diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_jobs.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_jobs.tsx deleted file mode 100644 index 0f386f416b86..000000000000 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_jobs.tsx +++ /dev/null @@ -1,169 +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 createContainer from 'constate'; -import { useMemo, useCallback, useEffect } from 'react'; - -import { callGetMlModuleAPI } from './api/ml_get_module'; -import { bucketSpan, getJobId } from '../../../../common/log_analysis'; -import { useTrackedPromise } from '../../../utils/use_tracked_promise'; -import { callJobsSummaryAPI } from './api/ml_get_jobs_summary_api'; -import { callSetupMlModuleAPI, SetupMlModuleResponsePayload } from './api/ml_setup_module_api'; -import { useLogAnalysisCleanup } from './log_analysis_cleanup'; -import { useStatusState } from './log_analysis_status_state'; - -const MODULE_ID = 'logs_ui_analysis'; - -export const useLogAnalysisJobs = ({ - indexPattern, - sourceId, - spaceId, - timeField, -}: { - indexPattern: string; - sourceId: string; - spaceId: string; - timeField: string; -}) => { - const { cleanupMLResources } = useLogAnalysisCleanup({ sourceId, spaceId }); - const [statusState, dispatch] = useStatusState({ - bucketSpan, - indexPattern, - timestampField: timeField, - }); - - const [fetchModuleDefinitionRequest, fetchModuleDefinition] = useTrackedPromise( - { - cancelPreviousOn: 'resolution', - createPromise: async () => { - dispatch({ type: 'fetchingModuleDefinition' }); - return await callGetMlModuleAPI(MODULE_ID); - }, - onResolve: response => { - dispatch({ - type: 'fetchedModuleDefinition', - spaceId, - sourceId, - moduleDefinition: response, - }); - }, - onReject: () => { - dispatch({ type: 'failedFetchingModuleDefinition' }); - }, - }, - [] - ); - - const [setupMlModuleRequest, setupMlModule] = useTrackedPromise( - { - cancelPreviousOn: 'resolution', - createPromise: async ( - indices: string[], - start: number | undefined, - end: number | undefined - ) => { - dispatch({ type: 'startedSetup' }); - return await callSetupMlModuleAPI( - MODULE_ID, - start, - end, - spaceId, - sourceId, - indices.join(','), - timeField, - bucketSpan - ); - }, - onResolve: ({ datafeeds, jobs }: SetupMlModuleResponsePayload) => { - dispatch({ type: 'finishedSetup', datafeeds, jobs, spaceId, sourceId }); - }, - onReject: () => { - dispatch({ type: 'failedSetup' }); - }, - }, - [spaceId, sourceId, timeField, bucketSpan] - ); - - const [fetchJobStatusRequest, fetchJobStatus] = useTrackedPromise( - { - cancelPreviousOn: 'resolution', - createPromise: async () => { - dispatch({ type: 'fetchingJobStatuses' }); - return await callJobsSummaryAPI(spaceId, sourceId); - }, - onResolve: jobResponse => { - dispatch({ type: 'fetchedJobStatuses', payload: jobResponse, spaceId, sourceId }); - }, - onReject: err => { - dispatch({ type: 'failedFetchingJobStatuses' }); - }, - }, - [spaceId, sourceId] - ); - - const isLoadingSetupStatus = useMemo( - () => - fetchJobStatusRequest.state === 'pending' || fetchModuleDefinitionRequest.state === 'pending', - [fetchJobStatusRequest.state, fetchModuleDefinitionRequest.state] - ); - - const availableIndices = useMemo(() => indexPattern.split(','), [indexPattern]); - - const viewResults = useCallback(() => { - dispatch({ type: 'viewedResults' }); - }, []); - - const cleanupAndSetup = useCallback( - (indices: string[], start: number | undefined, end: number | undefined) => { - dispatch({ type: 'startedSetup' }); - cleanupMLResources() - .then(() => { - setupMlModule(indices, start, end); - }) - .catch(() => { - dispatch({ type: 'failedSetup' }); - }); - }, - [cleanupMLResources, setupMlModule] - ); - - const viewSetupForReconfiguration = useCallback(() => { - dispatch({ type: 'requestedJobConfigurationUpdate' }); - }, []); - - const viewSetupForUpdate = useCallback(() => { - dispatch({ type: 'requestedJobDefinitionUpdate' }); - }, []); - - useEffect(() => { - fetchModuleDefinition(); - }, [fetchModuleDefinition]); - - const jobIds = useMemo(() => { - return { - 'log-entry-rate': getJobId(spaceId, sourceId, 'log-entry-rate'), - }; - }, [sourceId, spaceId]); - - return { - availableIndices, - fetchJobStatus, - isLoadingSetupStatus, - jobStatus: statusState.jobStatus, - lastSetupErrorMessages: statusState.lastSetupErrorMessages, - cleanupAndSetup, - setup: setupMlModule, - setupMlModuleRequest, - setupStatus: statusState.setupStatus, - timestampField: timeField, - viewSetupForReconfiguration, - viewSetupForUpdate, - viewResults, - jobIds, - }; -}; - -export const LogAnalysisJobs = createContainer(useLogAnalysisJobs); diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_module.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_module.tsx new file mode 100644 index 000000000000..d7d0ecb6f2c8 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_module.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 { useCallback, useMemo } from 'react'; + +import { useTrackedPromise } from '../../../utils/use_tracked_promise'; +import { useModuleStatus } from './log_analysis_module_status'; +import { ModuleDescriptor, ModuleSourceConfiguration } from './log_analysis_module_types'; + +export const useLogAnalysisModule = ({ + sourceConfiguration, + moduleDescriptor, +}: { + sourceConfiguration: ModuleSourceConfiguration; + moduleDescriptor: ModuleDescriptor; +}) => { + const { spaceId, sourceId, timestampField, indices } = sourceConfiguration; + const [moduleStatus, dispatchModuleStatus] = useModuleStatus(moduleDescriptor.jobTypes, { + bucketSpan: moduleDescriptor.bucketSpan, + indexPattern: indices.join(','), + timestampField, + }); + + const [fetchModuleDefinitionRequest, fetchModuleDefinition] = useTrackedPromise( + { + cancelPreviousOn: 'resolution', + createPromise: async () => { + dispatchModuleStatus({ type: 'fetchingModuleDefinition' }); + return await moduleDescriptor.getModuleDefinition(); + }, + onResolve: response => { + dispatchModuleStatus({ + type: 'fetchedModuleDefinition', + spaceId, + sourceId, + moduleDefinition: response, + }); + }, + onReject: () => { + dispatchModuleStatus({ type: 'failedFetchingModuleDefinition' }); + }, + }, + [moduleDescriptor.getModuleDefinition, spaceId, sourceId] + ); + + const [fetchJobStatusRequest, fetchJobStatus] = useTrackedPromise( + { + cancelPreviousOn: 'resolution', + createPromise: async () => { + dispatchModuleStatus({ type: 'fetchingJobStatuses' }); + return await moduleDescriptor.getJobSummary(spaceId, sourceId); + }, + onResolve: jobResponse => { + dispatchModuleStatus({ + type: 'fetchedJobStatuses', + payload: jobResponse, + spaceId, + sourceId, + }); + }, + onReject: () => { + dispatchModuleStatus({ type: 'failedFetchingJobStatuses' }); + }, + }, + [spaceId, sourceId] + ); + + const isLoadingModuleStatus = useMemo( + () => + fetchJobStatusRequest.state === 'pending' || fetchModuleDefinitionRequest.state === 'pending', + [fetchJobStatusRequest.state, fetchModuleDefinitionRequest.state] + ); + + const [, setUpModule] = useTrackedPromise( + { + cancelPreviousOn: 'resolution', + createPromise: async ( + selectedIndices: string[], + start: number | undefined, + end: number | undefined + ) => { + dispatchModuleStatus({ type: 'startedSetup' }); + return await moduleDescriptor.setUpModule(start, end, { + indices: selectedIndices, + sourceId, + spaceId, + timestampField, + }); + }, + onResolve: ({ datafeeds, jobs }) => { + dispatchModuleStatus({ type: 'finishedSetup', datafeeds, jobs, spaceId, sourceId }); + }, + onReject: () => { + dispatchModuleStatus({ type: 'failedSetup' }); + }, + }, + [moduleDescriptor.setUpModule, spaceId, sourceId, timestampField] + ); + + const [cleanUpModuleRequest, cleanUpModule] = useTrackedPromise( + { + cancelPreviousOn: 'resolution', + createPromise: async () => { + return await moduleDescriptor.cleanUpModule(spaceId, sourceId); + }, + }, + [spaceId, sourceId] + ); + + const isCleaningUp = useMemo(() => cleanUpModuleRequest.state === 'pending', [ + cleanUpModuleRequest.state, + ]); + + const cleanUpAndSetUpModule = useCallback( + (selectedIndices: string[], start: number | undefined, end: number | undefined) => { + dispatchModuleStatus({ type: 'startedSetup' }); + cleanUpModule() + .then(() => { + setUpModule(selectedIndices, start, end); + }) + .catch(() => { + dispatchModuleStatus({ type: 'failedSetup' }); + }); + }, + [cleanUpModule, dispatchModuleStatus, setUpModule] + ); + + const viewSetupForReconfiguration = useCallback(() => { + dispatchModuleStatus({ type: 'requestedJobConfigurationUpdate' }); + }, [dispatchModuleStatus]); + + const viewSetupForUpdate = useCallback(() => { + dispatchModuleStatus({ type: 'requestedJobDefinitionUpdate' }); + }, [dispatchModuleStatus]); + + const viewResults = useCallback(() => { + dispatchModuleStatus({ type: 'viewedResults' }); + }, [dispatchModuleStatus]); + + const jobIds = useMemo(() => moduleDescriptor.getJobIds(spaceId, sourceId), [ + moduleDescriptor, + spaceId, + sourceId, + ]); + + return { + cleanUpAndSetUpModule, + cleanUpModule, + fetchJobStatus, + fetchModuleDefinition, + isCleaningUp, + isLoadingModuleStatus, + jobIds, + jobStatus: moduleStatus.jobStatus, + lastSetupErrorMessages: moduleStatus.lastSetupErrorMessages, + moduleDescriptor, + setUpModule, + setupStatus: moduleStatus.setupStatus, + sourceConfiguration, + viewResults, + viewSetupForReconfiguration, + viewSetupForUpdate, + }; +}; diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_status_state.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_module_status.tsx similarity index 70% rename from x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_status_state.tsx rename to x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_module_status.tsx index 1f4c924ea3da..6d634538cd7f 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_status_state.tsx +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_module_status.tsx @@ -7,20 +7,19 @@ import { useReducer } from 'react'; import { + JobSourceConfiguration, + JobStatus, + SetupStatus, getDatafeedId, getJobId, isJobStatusWithResults, - JobStatus, - JobType, - jobTypeRT, - SetupStatus, } from '../../../../common/log_analysis'; import { FetchJobStatusResponsePayload, JobSummary } from './api/ml_get_jobs_summary_api'; import { GetMlModuleResponsePayload, JobDefinition } from './api/ml_get_module'; import { SetupMlModuleResponsePayload } from './api/ml_setup_module_api'; import { MandatoryProperty } from '../../../../common/utility_types'; -interface StatusReducerState { +interface StatusReducerState { jobDefinitions: JobDefinition[]; jobStatus: Record; jobSummaries: JobSummary[]; @@ -65,42 +64,60 @@ type StatusReducerAction = | { type: 'requestedJobDefinitionUpdate' } | { type: 'viewedResults' }; -const createInitialState = (sourceConfiguration: JobSourceConfiguration): StatusReducerState => ({ +const createInitialState = ({ + jobTypes, + sourceConfiguration, +}: { + jobTypes: JobType[]; + sourceConfiguration: JobSourceConfiguration; +}): StatusReducerState => ({ jobDefinitions: [], - jobStatus: { - 'log-entry-rate': 'unknown', - }, + jobStatus: jobTypes.reduce( + (accumulatedJobStatus, jobType) => ({ + ...accumulatedJobStatus, + [jobType]: 'unknown', + }), + {} as Record + ), jobSummaries: [], lastSetupErrorMessages: [], setupStatus: 'initializing', sourceConfiguration, }); -function statusReducer(state: StatusReducerState, action: StatusReducerAction): StatusReducerState { +const createStatusReducer = (jobTypes: JobType[]) => ( + state: StatusReducerState, + action: StatusReducerAction +): StatusReducerState => { switch (action.type) { case 'startedSetup': { return { ...state, - jobStatus: { - 'log-entry-rate': 'initializing', - }, + jobStatus: jobTypes.reduce( + (accumulatedJobStatus, jobType) => ({ + ...accumulatedJobStatus, + [jobType]: 'initializing', + }), + {} as Record + ), setupStatus: 'pending', }; } case 'finishedSetup': { const { jobs, datafeeds, spaceId, sourceId } = action; - const nextJobStatus = { - ...state.jobStatus, - 'log-entry-rate': - hasSuccessfullyCreatedJob(getJobId(spaceId, sourceId, 'log-entry-rate'))(jobs) && - hasSuccessfullyStartedDatafeed(getDatafeedId(spaceId, sourceId, 'log-entry-rate'))( - datafeeds - ) - ? ('started' as JobStatus) - : ('failed' as JobStatus), - }; - const nextSetupStatus = Object.values(nextJobStatus).every(jobState => - ['started'].includes(jobState) + const nextJobStatus = jobTypes.reduce( + (accumulatedJobStatus, jobType) => ({ + ...accumulatedJobStatus, + [jobType]: + hasSuccessfullyCreatedJob(getJobId(spaceId, sourceId, jobType))(jobs) && + hasSuccessfullyStartedDatafeed(getDatafeedId(spaceId, sourceId, jobType))(datafeeds) + ? 'started' + : 'failed', + }), + {} as Record + ); + const nextSetupStatus = Object.values(nextJobStatus).every( + jobState => jobState === 'started' ) ? 'succeeded' : 'failed'; @@ -122,10 +139,13 @@ function statusReducer(state: StatusReducerState, action: StatusReducerAction): case 'failedSetup': { return { ...state, - jobStatus: { - ...state.jobStatus, - 'log-entry-rate': 'failed', - }, + jobStatus: jobTypes.reduce( + (accumulatedJobStatus, jobType) => ({ + ...accumulatedJobStatus, + [jobType]: 'failed', + }), + {} as Record + ), setupStatus: 'failed', }; } @@ -140,10 +160,13 @@ function statusReducer(state: StatusReducerState, action: StatusReducerAction): const { payload: jobSummaries, spaceId, sourceId } = action; const { jobDefinitions, setupStatus, sourceConfiguration } = state; - const nextJobStatus = { - ...state.jobStatus, - 'log-entry-rate': getJobStatus(getJobId(spaceId, sourceId, 'log-entry-rate'))(jobSummaries), - }; + const nextJobStatus = jobTypes.reduce( + (accumulatedJobStatus, jobType) => ({ + ...accumulatedJobStatus, + [jobType]: getJobStatus(getJobId(spaceId, sourceId, jobType))(jobSummaries), + }), + {} as Record + ); const nextSetupStatus = getSetupStatus( spaceId, sourceId, @@ -164,10 +187,13 @@ function statusReducer(state: StatusReducerState, action: StatusReducerAction): return { ...state, setupStatus: 'unknown', - jobStatus: { - ...state.jobStatus, - 'log-entry-rate': 'unknown', - }, + jobStatus: jobTypes.reduce( + (accumulatedJobStatus, jobType) => ({ + ...accumulatedJobStatus, + [jobType]: 'unknown', + }), + {} as Record + ), }; } case 'fetchedModuleDefinition': { @@ -230,7 +256,7 @@ function statusReducer(state: StatusReducerState, action: StatusReducerAction): return state; } } -} +}; const hasSuccessfullyCreatedJob = (jobId: string) => ( jobSetupResponses: SetupMlModuleResponsePayload['jobs'] @@ -281,7 +307,7 @@ const getJobStatus = (jobId: string) => (jobSummaries: FetchJobStatusResponsePay } )[0] || 'missing'; -const getSetupStatus = ( +const getSetupStatus = ( spaceId: string, sourceId: string, sourceConfiguration: JobSourceConfiguration, @@ -289,44 +315,43 @@ const getSetupStatus = ( jobDefinitions: JobDefinition[], jobSummaries: JobSummary[] ) => (previousSetupStatus: SetupStatus) => - Object.entries(everyJobStatus).reduce((setupStatus, [jobType, jobStatus]) => { - if (!jobTypeRT.is(jobType)) { - return setupStatus; - } + Object.entries(everyJobStatus).reduce( + (setupStatus, [jobType, jobStatus]) => { + const jobId = getJobId(spaceId, sourceId, jobType); + const jobDefinition = jobDefinitions.find(({ id }) => id === jobType); - const jobId = getJobId(spaceId, sourceId, jobType); - const jobDefinition = jobDefinitions.find(({ id }) => id === jobType); + if (jobStatus === 'missing') { + return 'required'; + } else if ( + setupStatus === 'required' || + setupStatus === 'requiredForUpdate' || + setupStatus === 'requiredForReconfiguration' + ) { + return setupStatus; + } else if ( + setupStatus === 'skippedButUpdatable' || + (jobDefinition && + !isJobRevisionCurrent( + jobId, + jobDefinition.config.custom_settings.job_revision || 0 + )(jobSummaries)) + ) { + return 'skippedButUpdatable'; + } else if ( + setupStatus === 'skippedButReconfigurable' || + !isJobConfigurationConsistent(jobId, sourceConfiguration)(jobSummaries) + ) { + return 'skippedButReconfigurable'; + } else if (setupStatus === 'hiddenAfterSuccess') { + return setupStatus; + } else if (setupStatus === 'skipped' || isJobStatusWithResults(jobStatus)) { + return 'skipped'; + } - if (jobStatus === 'missing') { - return 'required'; - } else if ( - setupStatus === 'required' || - setupStatus === 'requiredForUpdate' || - setupStatus === 'requiredForReconfiguration' - ) { return setupStatus; - } else if ( - setupStatus === 'skippedButUpdatable' || - (jobDefinition && - !isJobRevisionCurrent( - jobId, - jobDefinition.config.custom_settings.job_revision || 0 - )(jobSummaries)) - ) { - return 'skippedButUpdatable'; - } else if ( - setupStatus === 'skippedButReconfigurable' || - !isJobConfigurationConsistent(jobId, sourceConfiguration)(jobSummaries) - ) { - return 'skippedButReconfigurable'; - } else if (setupStatus === 'hiddenAfterSuccess') { - return setupStatus; - } else if (setupStatus === 'skipped' || isJobStatusWithResults(jobStatus)) { - return 'skipped'; - } - - return setupStatus; - }, previousSetupStatus); + }, + previousSetupStatus + ); const isJobRevisionCurrent = (jobId: string, currentRevision: number) => ( jobSummaries: FetchJobStatusResponsePayload @@ -377,12 +402,13 @@ const isIndexPatternSubset = (indexPatternSubset: string, indexPatternSuperset: const hasError = (value: Value): value is MandatoryProperty => value.error != null; -export const useStatusState = (sourceConfiguration: JobSourceConfiguration) => { - return useReducer(statusReducer, sourceConfiguration, createInitialState); +export const useModuleStatus = ( + jobTypes: JobType[], + sourceConfiguration: JobSourceConfiguration +) => { + return useReducer( + createStatusReducer(jobTypes), + { jobTypes, sourceConfiguration }, + createInitialState + ); }; - -interface JobSourceConfiguration { - bucketSpan: number; - indexPattern: string; - timestampField: string; -} diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_module_types.ts b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_module_types.ts new file mode 100644 index 000000000000..dc9f25b49263 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_module_types.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 { DeleteJobsResponsePayload } from './api/ml_cleanup'; +import { FetchJobStatusResponsePayload } from './api/ml_get_jobs_summary_api'; +import { GetMlModuleResponsePayload } from './api/ml_get_module'; +import { SetupMlModuleResponsePayload } from './api/ml_setup_module_api'; +import { ValidationIndicesResponsePayload } from '../../../../common/http_api/log_analysis'; + +export interface ModuleDescriptor { + moduleId: string; + jobTypes: JobType[]; + bucketSpan: number; + getJobIds: (spaceId: string, sourceId: string) => Record; + getJobSummary: (spaceId: string, sourceId: string) => Promise; + getModuleDefinition: () => Promise; + setUpModule: ( + start: number | undefined, + end: number | undefined, + sourceConfiguration: ModuleSourceConfiguration + ) => Promise; + cleanUpModule: (spaceId: string, sourceId: string) => Promise; + validateSetupIndices: ( + sourceConfiguration: ModuleSourceConfiguration + ) => Promise; +} + +export interface ModuleSourceConfiguration { + indices: string[]; + sourceId: string; + spaceId: string; + timestampField: string; +} diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_setup_state.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_setup_state.tsx index c965c50bedcc..74dbb3c7a806 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_setup_state.tsx +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_setup_state.tsx @@ -4,15 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { useState, useCallback, useMemo, useEffect } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; +import { ValidationIndicesError } from '../../../../common/http_api'; import { isExampleDataIndex } from '../../../../common/log_analysis'; -import { - ValidationIndicesError, - ValidationIndicesResponsePayload, -} from '../../../../common/http_api'; import { useTrackedPromise } from '../../../utils/use_tracked_promise'; -import { callIndexPatternsValidate } from './api/index_patterns_validate'; +import { ModuleDescriptor, ModuleSourceConfiguration } from './log_analysis_module_types'; type SetupHandler = ( indices: string[], @@ -25,53 +22,69 @@ export type ValidationIndicesUIError = | { error: 'NETWORK_ERROR' } | { error: 'TOO_FEW_SELECTED_INDICES' }; -export interface ValidatedIndex { - index: string; - errors: ValidationIndicesError[]; +interface ValidIndex { + validity: 'valid'; + name: string; isSelected: boolean; } -interface AnalysisSetupStateArguments { - availableIndices: string[]; +interface InvalidIndex { + validity: 'invalid'; + name: string; + errors: ValidationIndicesError[]; +} + +export type ValidatedIndex = ValidIndex | InvalidIndex; + +interface AnalysisSetupStateArguments { cleanupAndSetupModule: SetupHandler; + moduleDescriptor: ModuleDescriptor; setupModule: SetupHandler; - timestampField: string; + sourceConfiguration: ModuleSourceConfiguration; } const fourWeeksInMs = 86400000 * 7 * 4; -export const useAnalysisSetupState = ({ - availableIndices, +export const useAnalysisSetupState = ({ cleanupAndSetupModule, + moduleDescriptor: { validateSetupIndices }, setupModule, - timestampField, -}: AnalysisSetupStateArguments) => { + sourceConfiguration, +}: AnalysisSetupStateArguments) => { const [startTime, setStartTime] = useState(Date.now() - fourWeeksInMs); const [endTime, setEndTime] = useState(undefined); - // Prepare the validation - const [validatedIndices, setValidatedIndices] = useState( - availableIndices.map(index => ({ - index, - errors: [], - isSelected: false, - })) - ); + const [validatedIndices, setValidatedIndices] = useState([]); + const [validateIndicesRequest, validateIndices] = useTrackedPromise( { cancelPreviousOn: 'resolution', createPromise: async () => { - return await callIndexPatternsValidate(timestampField, availableIndices); + return await validateSetupIndices(sourceConfiguration); }, - onResolve: ({ data }: ValidationIndicesResponsePayload) => { - setValidatedIndices( - availableIndices.map(index => { - const errors = data.errors.filter(error => error.index === index); - return { - index, - errors, - isSelected: errors.length === 0 && !isExampleDataIndex(index), - }; + onResolve: ({ data: { errors } }) => { + setValidatedIndices(previousValidatedIndices => + sourceConfiguration.indices.map(indexName => { + const previousValidatedIndex = previousValidatedIndices.filter( + ({ name }) => name === indexName + )[0]; + const indexValiationErrors = errors.filter(({ index }) => index === indexName); + if (indexValiationErrors.length > 0) { + return { + validity: 'invalid', + name: indexName, + errors: indexValiationErrors, + }; + } else { + return { + validity: 'valid', + name: indexName, + isSelected: + previousValidatedIndex?.validity === 'valid' + ? previousValidatedIndex?.isSelected + : !isExampleDataIndex(indexName), + }; + } }) ); }, @@ -79,7 +92,7 @@ export const useAnalysisSetupState = ({ setValidatedIndices([]); }, }, - [availableIndices, timestampField] + [sourceConfiguration.indices] ); useEffect(() => { @@ -87,7 +100,10 @@ export const useAnalysisSetupState = ({ }, [validateIndices]); const selectedIndexNames = useMemo( - () => validatedIndices.filter(i => i.isSelected).map(i => i.index), + () => + validatedIndices + .filter(index => index.validity === 'valid' && index.isSelected) + .map(i => i.name), [validatedIndices] ); @@ -120,9 +136,11 @@ export const useAnalysisSetupState = ({ } return validatedIndices.reduce((errors, index) => { - return selectedIndexNames.includes(index.index) ? errors.concat(index.errors) : errors; + return index.validity === 'invalid' && selectedIndexNames.includes(index.name) + ? [...errors, ...index.errors] + : errors; }, []); - }, [selectedIndexNames, validatedIndices, validateIndicesRequest.state]); + }, [isValidating, validateIndicesRequest.state, selectedIndexNames, validatedIndices]); return { cleanupAndSetup, diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_entry_rate.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_entry_rate.tsx deleted file mode 100644 index 8b21a7e82989..000000000000 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_entry_rate.tsx +++ /dev/null @@ -1,50 +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 { useMemo, useState } from 'react'; - -import { GetLogEntryRateSuccessResponsePayload } from '../../../../common/http_api/log_analysis'; -import { useTrackedPromise } from '../../../utils/use_tracked_promise'; -import { callGetLogEntryRateAPI } from './api/get_log_entry_rate'; - -type LogEntryRateResults = GetLogEntryRateSuccessResponsePayload['data']; - -export const useLogEntryRate = ({ - sourceId, - startTime, - endTime, - bucketDuration, -}: { - sourceId: string; - startTime: number; - endTime: number; - bucketDuration: number; -}) => { - const [logEntryRate, setLogEntryRate] = useState(null); - - const [getLogEntryRateRequest, getLogEntryRate] = useTrackedPromise( - { - cancelPreviousOn: 'resolution', - createPromise: async () => { - return await callGetLogEntryRateAPI(sourceId, startTime, endTime, bucketDuration); - }, - onResolve: response => { - setLogEntryRate(response.data); - }, - }, - [sourceId, startTime, endTime, bucketDuration] - ); - - const isLoading = useMemo(() => getLogEntryRateRequest.state === 'pending', [ - getLogEntryRateRequest.state, - ]); - - return { - getLogEntryRate, - isLoading, - logEntryRate, - }; -}; diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/ml_api_types.ts b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/ml_api_types.ts deleted file mode 100644 index ee70edc31d49..000000000000 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/ml_api_types.ts +++ /dev/null @@ -1,28 +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 * as rt from 'io-ts'; - -export const getMlCapabilitiesResponsePayloadRT = rt.type({ - capabilities: rt.type({ - canGetJobs: rt.boolean, - canCreateJob: rt.boolean, - canDeleteJob: rt.boolean, - canOpenJob: rt.boolean, - canCloseJob: rt.boolean, - canForecastJob: rt.boolean, - canGetDatafeeds: rt.boolean, - canStartStopDatafeed: rt.boolean, - canUpdateJob: rt.boolean, - canUpdateDatafeed: rt.boolean, - canPreviewDatafeed: rt.boolean, - }), - isPlatinumOrTrialLicense: rt.boolean, - mlFeatureEnabledInSpace: rt.boolean, - upgradeInProgress: rt.boolean, -}); - -export type GetMlCapabilitiesResponsePayload = rt.TypeOf; diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/log_entry_highlights.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/log_entry_highlights.tsx index 6ead866fb960..2b19958a9b1a 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/log_entry_highlights.tsx +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/log_entry_highlights.tsx @@ -78,7 +78,7 @@ export const useLogEntryHighlights = ( } else { setLogEntryHighlights([]); } - }, [highlightTerms, startKey, endKey, filterQuery, sourceVersion]); + }, [endKey, filterQuery, highlightTerms, loadLogEntryHighlights, sourceVersion, startKey]); const logEntryHighlightsById = useMemo( () => diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/log_summary_highlights.ts b/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/log_summary_highlights.ts index 34c66afda010..874c70e01649 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/log_summary_highlights.ts +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/log_summary_highlights.ts @@ -74,7 +74,15 @@ export const useLogSummaryHighlights = ( } else { setLogSummaryHighlights([]); } - }, [highlightTerms, start, end, bucketSize, filterQuery, sourceVersion]); + }, [ + bucketSize, + debouncedLoadSummaryHighlights, + end, + filterQuery, + highlightTerms, + sourceVersion, + start, + ]); return { logSummaryHighlights, diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/next_and_previous.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/next_and_previous.tsx index 95ead50119eb..62a43a541282 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/next_and_previous.tsx +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/next_and_previous.tsx @@ -53,7 +53,7 @@ export const useNextAndPrevious = ({ const initialTimeKey = getUniqueLogEntryKey(entries[initialIndex]); setCurrentTimeKey(initialTimeKey); } - }, [currentTimeKey, entries, setCurrentTimeKey]); + }, [currentTimeKey, entries, setCurrentTimeKey, visibleMidpoint]); const indexOfCurrentTimeKey = useMemo(() => { if (currentTimeKey && entries.length > 0) { diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/redux_bridges.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/redux_bridges.tsx index 2b60c6edd97a..9ea8987d4f32 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/redux_bridges.tsx +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/redux_bridges.tsx @@ -25,11 +25,11 @@ export const LogHighlightsPositionBridge = withLogPosition( const { setJumpToTarget, setVisibleMidpoint } = useContext(LogHighlightsState.Context); useEffect(() => { setVisibleMidpoint(visibleMidpoint); - }, [visibleMidpoint]); + }, [setVisibleMidpoint, visibleMidpoint]); useEffect(() => { setJumpToTarget(() => jumpToTargetPosition); - }, [jumpToTargetPosition]); + }, [jumpToTargetPosition, setJumpToTarget]); return null; } @@ -41,7 +41,7 @@ export const LogHighlightsFilterQueryBridge = withLogFilter( useEffect(() => { setFilterQuery(serializedFilterQuery); - }, [serializedFilterQuery]); + }, [serializedFilterQuery, setFilterQuery]); return null; } diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/with_stream_items.ts b/x-pack/legacy/plugins/infra/public/containers/logs/with_stream_items.ts index da468b4391e4..9b20676486af 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/with_stream_items.ts +++ b/x-pack/legacy/plugins/infra/public/containers/logs/with_stream_items.ts @@ -35,7 +35,7 @@ export const WithStreamItems: React.FunctionComponent<{ createLogEntryStreamItem(logEntry, logEntryHighlightsById[logEntry.gid] || []) ), - [logEntries.entries, logEntryHighlightsById] + [isAutoReloading, logEntries.entries, logEntries.isReloading, logEntryHighlightsById] ); return children({ diff --git a/x-pack/legacy/plugins/infra/public/containers/metrics_explorer/use_metrics_explorer_data.ts b/x-pack/legacy/plugins/infra/public/containers/metrics_explorer/use_metrics_explorer_data.ts index 1418d6aef67a..c2a599ea1ae7 100644 --- a/x-pack/legacy/plugins/infra/public/containers/metrics_explorer/use_metrics_explorer_data.ts +++ b/x-pack/legacy/plugins/infra/public/containers/metrics_explorer/use_metrics_explorer_data.ts @@ -96,6 +96,9 @@ export function useMetricsExplorerData( } setLoading(false); })(); + + // TODO: fix this dependency list while preserving the semantics + // eslint-disable-next-line react-hooks/exhaustive-deps }, [options, source, timerange, signal, afterKey]); return { error, loading, data }; } diff --git a/x-pack/legacy/plugins/infra/public/containers/metrics_explorer/use_metrics_explorer_options.ts b/x-pack/legacy/plugins/infra/public/containers/metrics_explorer/use_metrics_explorer_options.ts index 278f3e0a9c17..de7a8d5805ec 100644 --- a/x-pack/legacy/plugins/infra/public/containers/metrics_explorer/use_metrics_explorer_options.ts +++ b/x-pack/legacy/plugins/infra/public/containers/metrics_explorer/use_metrics_explorer_options.ts @@ -102,7 +102,7 @@ function useStateWithLocalStorage( const [state, setState] = useState(parseJsonOrDefault(storageState, defaultState)); useEffect(() => { localStorage.setItem(key, JSON.stringify(state)); - }, [state]); + }, [key, state]); return [state, setState]; } diff --git a/x-pack/legacy/plugins/infra/public/containers/source/index.ts b/x-pack/legacy/plugins/infra/public/containers/source/index.ts index 9442836f2a6c..5911decf2177 100644 --- a/x-pack/legacy/plugins/infra/public/containers/source/index.ts +++ b/x-pack/legacy/plugins/infra/public/containers/source/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { Source, useSource } from './source'; +export * from './source'; diff --git a/x-pack/legacy/plugins/infra/public/containers/source/source.tsx b/x-pack/legacy/plugins/infra/public/containers/source/source.tsx index 955529c9759c..4729f7aa31f0 100644 --- a/x-pack/legacy/plugins/infra/public/containers/source/source.tsx +++ b/x-pack/legacy/plugins/infra/public/containers/source/source.tsx @@ -176,3 +176,4 @@ export const useSource = ({ sourceId }: { sourceId: string }) => { }; export const Source = createContainer(useSource); +export const [SourceProvider, useSourceContext] = Source; diff --git a/x-pack/legacy/plugins/infra/public/hooks/use_saved_view.ts b/x-pack/legacy/plugins/infra/public/hooks/use_saved_view.ts index 8db0ed28d9b2..4b12b6c51ea0 100644 --- a/x-pack/legacy/plugins/infra/public/hooks/use_saved_view.ts +++ b/x-pack/legacy/plugins/infra/public/hooks/use_saved_view.ts @@ -26,29 +26,32 @@ export const useSavedView = (defaultViewState: ViewState, viewType: s >(viewType); const { create, error: errorOnCreate, createdId } = useCreateSavedObject(viewType); const { deleteObject, deletedId } = useDeleteSavedObject(viewType); - const deleteView = useCallback((id: string) => deleteObject(id), []); + const deleteView = useCallback((id: string) => deleteObject(id), [deleteObject]); const [createError, setCreateError] = useState(null); - useEffect(() => setCreateError(createError), [errorOnCreate, setCreateError]); + useEffect(() => setCreateError(errorOnCreate), [errorOnCreate]); - const saveView = useCallback((d: { [p: string]: any }) => { - const doSave = async () => { - const exists = await hasView(d.name); - if (exists) { - setCreateError( - i18n.translate('xpack.infra.savedView.errorOnCreate.duplicateViewName', { - defaultMessage: `A view with that name already exists.`, - }) - ); - return; - } - create(d); - }; - setCreateError(null); - doSave(); - }, []); + const saveView = useCallback( + (d: { [p: string]: any }) => { + const doSave = async () => { + const exists = await hasView(d.name); + if (exists) { + setCreateError( + i18n.translate('xpack.infra.savedView.errorOnCreate.duplicateViewName', { + defaultMessage: `A view with that name already exists.`, + }) + ); + return; + } + create(d); + }; + setCreateError(null); + doSave(); + }, + [create, hasView] + ); - const savedObjects = data ? data.savedObjects : []; + const savedObjects = useMemo(() => (data ? data.savedObjects : []), [data]); const views = useMemo(() => { const items: Array> = [ { @@ -61,19 +64,17 @@ export const useSavedView = (defaultViewState: ViewState, viewType: s }, ]; - if (data) { - data.savedObjects.forEach( - o => - o.type === viewType && - items.push({ - ...o.attributes, - id: o.id, - }) - ); - } + savedObjects.forEach( + o => + o.type === viewType && + items.push({ + ...o.attributes, + id: o.id, + }) + ); return items; - }, [savedObjects, defaultViewState]); + }, [defaultViewState, savedObjects, viewType]); return { views, diff --git a/x-pack/legacy/plugins/infra/public/hooks/use_track_metric.tsx b/x-pack/legacy/plugins/infra/public/hooks/use_track_metric.tsx index 379b3af3f106..c5945ab80820 100644 --- a/x-pack/legacy/plugins/infra/public/hooks/use_track_metric.tsx +++ b/x-pack/legacy/plugins/infra/public/hooks/use_track_metric.tsx @@ -57,6 +57,9 @@ export function useTrackMetric( const trackUiMetric = getTrackerForApp(app); const id = setTimeout(() => trackUiMetric(metricType, decoratedMetric), Math.max(delay, 0)); return () => clearTimeout(id); + + // the dependencies are managed externally + // eslint-disable-next-line react-hooks/exhaustive-deps }, effectDependencies); } diff --git a/x-pack/legacy/plugins/infra/public/pages/infrastructure/index.tsx b/x-pack/legacy/plugins/infra/public/pages/infrastructure/index.tsx index fe48fcc62f77..9efbbe790abc 100644 --- a/x-pack/legacy/plugins/infra/public/pages/infrastructure/index.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/infrastructure/index.tsx @@ -24,6 +24,7 @@ import { MetricsExplorerPage } from './metrics_explorer'; import { SnapshotPage } from './snapshot'; import { SettingsPage } from '../shared/settings'; import { AppNavigation } from '../../components/navigation/app_navigation'; +import { SourceLoadingPage } from '../../components/source_loading_page'; interface InfrastructurePageProps extends RouteComponentProps { uiCapabilities: UICapabilities; @@ -95,11 +96,15 @@ export const InfrastructurePage = injectUICapabilities( {({ configuration, createDerivedIndexPattern }) => ( - + {configuration ? ( + + ) : ( + + )} )} diff --git a/x-pack/legacy/plugins/infra/public/pages/infrastructure/metrics_explorer/index.tsx b/x-pack/legacy/plugins/infra/public/pages/infrastructure/metrics_explorer/index.tsx index 63f5a8196761..4db4319b91d3 100644 --- a/x-pack/legacy/plugins/infra/public/pages/infrastructure/metrics_explorer/index.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/infrastructure/metrics_explorer/index.tsx @@ -11,22 +11,17 @@ import { IIndexPattern } from 'src/plugins/data/public'; import { DocumentTitle } from '../../../components/document_title'; import { MetricsExplorerCharts } from '../../../components/metrics_explorer/charts'; import { MetricsExplorerToolbar } from '../../../components/metrics_explorer/toolbar'; -import { SourceLoadingPage } from '../../../components/source_loading_page'; import { SourceQuery } from '../../../../common/graphql/types'; import { NoData } from '../../../components/empty_states'; import { useMetricsExplorerState } from './use_metric_explorer_state'; import { useTrackPageview } from '../../../hooks/use_track_metric'; interface MetricsExplorerPageProps { - source: SourceQuery.Query['source']['configuration'] | undefined; + source: SourceQuery.Query['source']['configuration']; derivedIndexPattern: IIndexPattern; } export const MetricsExplorerPage = ({ source, derivedIndexPattern }: MetricsExplorerPageProps) => { - if (!source) { - return ; - } - const { loading, error, diff --git a/x-pack/legacy/plugins/infra/public/pages/infrastructure/metrics_explorer/use_metric_explorer_state.ts b/x-pack/legacy/plugins/infra/public/pages/infrastructure/metrics_explorer/use_metric_explorer_state.ts index 415a6ae89a8b..57ea88616970 100644 --- a/x-pack/legacy/plugins/infra/public/pages/infrastructure/metrics_explorer/use_metric_explorer_state.ts +++ b/x-pack/legacy/plugins/infra/public/pages/infrastructure/metrics_explorer/use_metric_explorer_state.ts @@ -59,7 +59,7 @@ export const useMetricsExplorerState = ( setAfterKey(null); setTimeRange({ ...currentTimerange, from: start, to: end }); }, - [currentTimerange] + [currentTimerange, setTimeRange] ); const handleGroupByChange = useCallback( @@ -70,7 +70,7 @@ export const useMetricsExplorerState = ( groupBy: groupBy || void 0, }); }, - [options] + [options, setOptions] ); const handleFilterQuerySubmit = useCallback( @@ -81,7 +81,7 @@ export const useMetricsExplorerState = ( filterQuery: query, }); }, - [options] + [options, setOptions] ); const handleMetricsChange = useCallback( @@ -92,7 +92,7 @@ export const useMetricsExplorerState = ( metrics, }); }, - [options] + [options, setOptions] ); const handleAggregationChange = useCallback( @@ -109,7 +109,7 @@ export const useMetricsExplorerState = ( })); setOptions({ ...options, aggregation, metrics }); }, - [options] + [options, setOptions] ); const onViewStateChange = useCallback( @@ -124,7 +124,7 @@ export const useMetricsExplorerState = ( setOptions(vs.options); } }, - [setChartOptions, setTimeRange, setTimeRange] + [setChartOptions, setOptions, setTimeRange] ); return { diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/index.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/index.tsx index 1630de11bbdf..4eddecf732f7 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/index.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/index.tsx @@ -21,7 +21,7 @@ import { Source, useSource } from '../../containers/source'; import { StreamPage } from './stream'; import { SettingsPage } from '../shared/settings'; import { AppNavigation } from '../../components/navigation/app_navigation'; -import { AnalysisPage } from './analysis'; +import { LogEntryRatePage } from './log_entry_rate'; import { useLogAnalysisCapabilities, LogAnalysisCapabilities, @@ -98,7 +98,7 @@ export const LogsPage = injectUICapabilities(({ match, uiCapabilities }: LogsPag - + + jobTypes.reduce( + (accumulatedJobIds, jobType) => ({ + ...accumulatedJobIds, + [jobType]: getJobId(spaceId, sourceId, jobType), + }), + {} as Record + ); + +const getJobSummary = async (spaceId: string, sourceId: string) => { + const response = await callJobsSummaryAPI(spaceId, sourceId, jobTypes); + const jobIds = Object.values(getJobIds(spaceId, sourceId)); + + return response.filter(jobSummary => jobIds.includes(jobSummary.id)); +}; + +const getModuleDefinition = async () => { + return await callGetMlModuleAPI(moduleId); +}; + +const setUpModule = async ( + start: number | undefined, + end: number | undefined, + { spaceId, sourceId, indices, timestampField }: ModuleSourceConfiguration +) => { + const indexNamePattern = indices.join(','); + const jobOverrides = [ + { + job_id: 'log-entry-rate' as const, + analysis_config: { + bucket_span: `${bucketSpan}ms`, + }, + data_description: { + time_field: timestampField, + }, + custom_settings: { + logs_source_config: { + indexPattern: indexNamePattern, + timestampField, + bucketSpan, + }, + }, + }, + ]; + + return callSetupMlModuleAPI( + moduleId, + start, + end, + spaceId, + sourceId, + indexNamePattern, + jobOverrides + ); +}; + +const cleanUpModule = async (spaceId: string, sourceId: string) => { + return await cleanUpJobsAndDatafeeds(spaceId, sourceId, jobTypes); +}; + +const validateSetupIndices = async ({ indices, timestampField }: ModuleSourceConfiguration) => { + return await callValidateIndicesAPI(indices, [ + { + name: timestampField, + validTypes: ['date'], + }, + { + name: partitionField, + validTypes: ['keyword'], + }, + ]); +}; + +export const logEntryRateModule: ModuleDescriptor = { + moduleId, + jobTypes, + bucketSpan, + getJobIds, + getJobSummary, + getModuleDefinition, + setUpModule, + cleanUpModule, + validateSetupIndices, +}; diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/page.tsx similarity index 53% rename from x-pack/legacy/plugins/infra/public/pages/logs/analysis/page.tsx rename to x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/page.tsx index d82da895f9a5..5ff5cd4db716 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/page.tsx @@ -7,15 +7,15 @@ import React from 'react'; import { ColumnarPage } from '../../../components/page'; -import { AnalysisPageContent } from './page_content'; -import { AnalysisPageProviders } from './page_providers'; +import { LogEntryRatePageContent } from './page_content'; +import { LogEntryRatePageProviders } from './page_providers'; -export const AnalysisPage = () => { +export const LogEntryRatePage = () => { return ( - - - + + + - + ); }; diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_content.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/page_content.tsx similarity index 58% rename from x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_content.tsx rename to x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/page_content.tsx index f0a26eae25ec..e71985f73fbb 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_content.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/page_content.tsx @@ -9,34 +9,37 @@ import React, { useContext, useEffect } from 'react'; import { isSetupStatusWithResults } from '../../../../common/log_analysis'; import { LoadingPage } from '../../../components/loading_page'; -import { LogAnalysisCapabilities, LogAnalysisJobs } from '../../../containers/logs/log_analysis'; +import { LogAnalysisCapabilities } from '../../../containers/logs/log_analysis'; import { Source } from '../../../containers/source'; -import { AnalysisResultsContent } from './page_results_content'; -import { AnalysisSetupContent } from './page_setup_content'; -import { AnalysisUnavailableContent } from './page_unavailable_content'; -import { AnalysisSetupStatusUnknownContent } from './page_setup_status_unknown'; +import { LogEntryRateResultsContent } from './page_results_content'; +import { LogEntryRateSetupContent } from './page_setup_content'; +import { LogEntryRateUnavailableContent } from './page_unavailable_content'; +import { LogEntryRateSetupStatusUnknownContent } from './page_setup_status_unknown'; +import { useLogEntryRateModuleContext } from './use_log_entry_rate_module'; -export const AnalysisPageContent = () => { +export const LogEntryRatePageContent = () => { const { sourceId } = useContext(Source.Context); const { hasLogAnalysisCapabilites } = useContext(LogAnalysisCapabilities.Context); const { - availableIndices, - cleanupAndSetup, + cleanUpAndSetUpModule: cleanupAndSetup, fetchJobStatus, + fetchModuleDefinition, lastSetupErrorMessages, - setup, + moduleDescriptor, + setUpModule, setupStatus, - timestampField, + sourceConfiguration, viewResults, - } = useContext(LogAnalysisJobs.Context); + } = useLogEntryRateModuleContext(); useEffect(() => { + fetchModuleDefinition(); fetchJobStatus(); - }, []); + }, [fetchJobStatus, fetchModuleDefinition]); if (!hasLogAnalysisCapabilites) { - return ; + return ; } else if (setupStatus === 'initializing') { return ( { /> ); } else if (setupStatus === 'unknown') { - return ; + return ; } else if (isSetupStatusWithResults(setupStatus)) { return ( - ); } else { return ( - ); diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_providers.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/page_providers.tsx similarity index 53% rename from x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_providers.tsx rename to x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/page_providers.tsx index fba32f6cbd6d..67c8ea7660a2 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_providers.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/page_providers.tsx @@ -4,24 +4,24 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useContext } from 'react'; +import React from 'react'; -import { LogAnalysisJobs } from '../../../containers/logs/log_analysis'; -import { Source } from '../../../containers/source'; +import { useSourceContext } from '../../../containers/source'; import { useKibanaSpaceId } from '../../../utils/use_kibana_space_id'; +import { LogEntryRateModuleProvider } from './use_log_entry_rate_module'; -export const AnalysisPageProviders: React.FunctionComponent = ({ children }) => { - const { sourceId, source } = useContext(Source.Context); +export const LogEntryRatePageProviders: React.FunctionComponent = ({ children }) => { + const { sourceId, source } = useSourceContext(); const spaceId = useKibanaSpaceId(); return ( - {children} - + ); }; diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/page_results_content.tsx similarity index 78% rename from x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx rename to x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/page_results_content.tsx index 7fa9ff3c93db..be637bc29a0d 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/page_results_content.tsx @@ -17,36 +17,36 @@ import { import numeral from '@elastic/numeral'; import { FormattedMessage } from '@kbn/i18n/react'; import moment from 'moment'; -import React, { useCallback, useContext, useMemo, useState } from 'react'; +import React, { useCallback, useMemo, useState, useEffect } from 'react'; import euiStyled from '../../../../../../common/eui_styled_components'; import { TimeRange } from '../../../../common/http_api/shared/time_range'; import { bucketSpan } from '../../../../common/log_analysis'; import { LoadingOverlayWrapper } from '../../../components/loading_overlay_wrapper'; -import { - LogAnalysisJobs, - StringTimeRange, - useLogAnalysisResults, - useLogAnalysisResultsUrlState, -} from '../../../containers/logs/log_analysis'; import { useInterval } from '../../../hooks/use_interval'; import { useTrackPageview } from '../../../hooks/use_track_metric'; import { useKibanaUiSetting } from '../../../utils/use_kibana_ui_setting'; import { FirstUseCallout } from './first_use'; import { AnomaliesResults } from './sections/anomalies'; import { LogRateResults } from './sections/log_rate'; +import { useLogEntryRateModuleContext } from './use_log_entry_rate_module'; +import { useLogEntryRateResults } from './use_log_entry_rate_results'; +import { + StringTimeRange, + useLogAnalysisResultsUrlState, +} from './use_log_entry_rate_results_url_state'; const JOB_STATUS_POLLING_INTERVAL = 30000; -export const AnalysisResultsContent = ({ +export const LogEntryRateResultsContent = ({ sourceId, isFirstUse, }: { sourceId: string; isFirstUse: boolean; }) => { - useTrackPageview({ app: 'infra_logs', path: 'analysis_results' }); - useTrackPageview({ app: 'infra_logs', path: 'analysis_results', delay: 15000 }); + useTrackPageview({ app: 'infra_logs', path: 'log_entry_rate_results' }); + useTrackPageview({ app: 'infra_logs', path: 'log_entry_rate_results', delay: 15000 }); const [dateFormat] = useKibanaUiSetting('dateFormat', 'MMMM D, YYYY h:mm A'); @@ -65,32 +65,20 @@ export const AnalysisResultsContent = ({ lastChangedTime: Date.now(), })); - const bucketDuration = useMemo(() => { - // This function takes the current time range in ms, - // works out the bucket interval we'd need to always - // display 100 data points, and then takes that new - // value and works out the nearest multiple of - // 900000 (15 minutes) to it, so that we don't end up with - // jaggy bucket boundaries between the ML buckets and our - // aggregation buckets. - const msRange = moment(queryTimeRange.value.endTime).diff( - moment(queryTimeRange.value.startTime) - ); - const bucketIntervalInMs = msRange / 100; - const result = bucketSpan * Math.round(bucketIntervalInMs / bucketSpan); - const roundedResult = parseInt(Number(result).toFixed(0), 10); - return roundedResult < bucketSpan ? bucketSpan : roundedResult; - }, [queryTimeRange.value.startTime, queryTimeRange.value.endTime]); + const bucketDuration = useMemo( + () => getBucketDuration(queryTimeRange.value.startTime, queryTimeRange.value.endTime), + [queryTimeRange.value.endTime, queryTimeRange.value.startTime] + ); - const { isLoading, logRateResults } = useLogAnalysisResults({ + const { getLogEntryRate, isLoading, logEntryRate } = useLogEntryRateResults({ sourceId, startTime: queryTimeRange.value.startTime, endTime: queryTimeRange.value.endTime, bucketDuration, - lastRequestTime: queryTimeRange.lastChangedTime, }); - const hasResults = useMemo(() => logRateResults && logRateResults.histogramBuckets.length > 0, [ - logRateResults, + + const hasResults = useMemo(() => (logEntryRate?.histogramBuckets?.length ?? 0) > 0, [ + logEntryRate, ]); const handleQueryTimeRangeChange = useCallback( @@ -145,7 +133,11 @@ export const AnalysisResultsContent = ({ viewSetupForReconfiguration, viewSetupForUpdate, jobIds, - } = useContext(LogAnalysisJobs.Context); + } = useLogEntryRateModuleContext(); + + useEffect(() => { + getLogEntryRate(); + }, [getLogEntryRate, queryTimeRange.lastChangedTime]); useInterval(() => { fetchJobStatus(); @@ -168,7 +160,7 @@ export const AnalysisResultsContent = ({ - {logRateResults ? ( + {logEntryRate ? ( - {numeral(logRateResults.totalNumberOfLogEntries).format('0.00a')} + {numeral(logEntryRate.totalNumberOfLogEntries).format('0.00a')} ), @@ -210,7 +202,7 @@ export const AnalysisResultsContent = ({ {isFirstUse && !hasResults ? : null} @@ -223,7 +215,7 @@ export const AnalysisResultsContent = ({ jobStatus={jobStatus['log-entry-rate']} viewSetupForReconfiguration={viewSetupForReconfiguration} viewSetupForUpdate={viewSetupForUpdate} - results={logRateResults} + results={logEntryRate} setTimeRange={handleChartTimeRangeChange} setupStatus={setupStatus} timeRange={queryTimeRange.value} @@ -250,6 +242,23 @@ const stringToNumericTimeRange = (timeRange: StringTimeRange): TimeRange => ({ ).valueOf(), }); +/** + * This function takes the current time range in ms, + * works out the bucket interval we'd need to always + * display 100 data points, and then takes that new + * value and works out the nearest multiple of + * 900000 (15 minutes) to it, so that we don't end up with + * jaggy bucket boundaries between the ML buckets and our + * aggregation buckets. + */ +const getBucketDuration = (startTime: number, endTime: number) => { + const msRange = moment(endTime).diff(moment(startTime)); + const bucketIntervalInMs = msRange / 100; + const result = bucketSpan * Math.round(bucketIntervalInMs / bucketSpan); + const roundedResult = parseInt(Number(result).toFixed(0), 10); + return roundedResult < bucketSpan ? bucketSpan : roundedResult; +}; + // This is needed due to the flex-basis: 100% !important; rule that // kicks in on small screens via media queries breaking when using direction="column" export const ResultsContentPage = euiStyled(EuiPage)` diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_setup_content.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/page_setup_content.tsx similarity index 70% rename from x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_setup_content.tsx rename to x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/page_setup_content.tsx index 7ae174c4a789..6c04404b9123 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_setup_content.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/page_setup_content.tsx @@ -4,23 +4,25 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; import { EuiPage, EuiPageBody, EuiPageContent, + EuiPageContentBody, EuiPageContentHeader, EuiPageContentHeaderSection, - EuiPageContentBody, + EuiSpacer, EuiText, EuiTitle, - EuiSpacer, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; +import React from 'react'; + import euiStyled from '../../../../../../common/eui_styled_components'; import { SetupStatus } from '../../../../common/log_analysis'; +import { ModuleDescriptor, ModuleSourceConfiguration } from '../../../containers/logs/log_analysis'; import { useTrackPageview } from '../../../hooks/use_track_metric'; -import { AnalysisSetupSteps } from './setup'; +import { LogEntryRateSetupSteps } from './setup'; type SetupHandler = ( indices: string[], @@ -28,32 +30,32 @@ type SetupHandler = ( endTime: number | undefined ) => void; -interface AnalysisSetupContentProps { - availableIndices: string[]; +interface LogEntryRateSetupContentProps { cleanupAndSetup: SetupHandler; errorMessages: string[]; + moduleDescriptor: ModuleDescriptor; setup: SetupHandler; setupStatus: SetupStatus; - timestampField: string; + sourceConfiguration: ModuleSourceConfiguration; viewResults: () => void; } -export const AnalysisSetupContent: React.FunctionComponent = ({ - availableIndices, +export const LogEntryRateSetupContent = ({ cleanupAndSetup, errorMessages, setup, setupStatus, - timestampField, viewResults, -}) => { - useTrackPageview({ app: 'infra_logs', path: 'analysis_setup' }); - useTrackPageview({ app: 'infra_logs', path: 'analysis_setup', delay: 15000 }); + moduleDescriptor, + sourceConfiguration, +}: LogEntryRateSetupContentProps) => { + useTrackPageview({ app: 'infra_logs', path: 'log_entry_rate_setup' }); + useTrackPageview({ app: 'infra_logs', path: 'log_entry_rate_setup', delay: 15000 }); return ( - + - - - + - + ); }; // !important due to https://github.com/elastic/eui/issues/2232 -const AnalysisPageContent = euiStyled(EuiPageContent)` +const LogEntryRateSetupPageContent = euiStyled(EuiPageContent)` max-width: 768px !important; `; -const AnalysisSetupPage = euiStyled(EuiPage)` +const LogEntryRateSetupPage = euiStyled(EuiPage)` height: 100%; `; diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_setup_status_unknown.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/page_setup_status_unknown.tsx similarity index 92% rename from x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_setup_status_unknown.tsx rename to x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/page_setup_status_unknown.tsx index 953b0841ffe9..4c685bd42b93 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_setup_status_unknown.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/page_setup_status_unknown.tsx @@ -14,7 +14,7 @@ interface Props { retry: () => void; } -export const AnalysisSetupStatusUnknownContent: React.FunctionComponent = ({ +export const LogEntryRateSetupStatusUnknownContent: React.FunctionComponent = ({ retry, }: Props) => ( = () => ( +export const LogEntryRateUnavailableContent: React.FunctionComponent<{}> = () => ( diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/anomalies/chart.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/chart.tsx similarity index 100% rename from x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/anomalies/chart.tsx rename to x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/chart.tsx diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/anomalies/expanded_row.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/expanded_row.tsx similarity index 84% rename from x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/anomalies/expanded_row.tsx rename to x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/expanded_row.tsx index 0586f5282ddf..f8a7f12364cf 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/anomalies/expanded_row.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/expanded_row.tsx @@ -4,38 +4,37 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useMemo } from 'react'; -import { i18n } from '@kbn/i18n'; +import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiStat } from '@elastic/eui'; import numeral from '@elastic/numeral'; -import { EuiFlexGroup, EuiFlexItem, EuiStat, EuiSpacer } from '@elastic/eui'; -import { AnomaliesChart } from './chart'; -import { LogRateResults } from '../../../../../containers/logs/log_analysis/log_analysis_results'; +import { i18n } from '@kbn/i18n'; +import React, { useMemo } from 'react'; + import { TimeRange } from '../../../../../../common/http_api/shared/time_range'; +import { AnalyzeInMlButton } from '../../../../../components/logging/log_analysis_results'; +import { LogEntryRateResults } from '../../use_log_entry_rate_results'; import { - getLogEntryRateSeriesForPartition, getAnnotationsForPartition, + getLogEntryRateSeriesForPartition, getTotalNumberOfLogEntriesForPartition, } from '../helpers/data_formatters'; -import { AnalyzeInMlButton } from '../analyze_in_ml_button'; +import { AnomaliesChart } from './chart'; export const AnomaliesTableExpandedRow: React.FunctionComponent<{ partitionId: string; topAnomalyScore: number; - results: LogRateResults; + results: LogEntryRateResults; setTimeRange: (timeRange: TimeRange) => void; timeRange: TimeRange; jobId: string; }> = ({ results, timeRange, setTimeRange, partitionId, jobId }) => { const logEntryRateSeries = useMemo( () => - results && results.histogramBuckets - ? getLogEntryRateSeriesForPartition(results, partitionId) - : [], + results?.histogramBuckets ? getLogEntryRateSeriesForPartition(results, partitionId) : [], [results, partitionId] ); const anomalyAnnotations = useMemo( () => - results && results.histogramBuckets + results?.histogramBuckets ? getAnnotationsForPartition(results, partitionId) : { warning: [], @@ -47,7 +46,7 @@ export const AnomaliesTableExpandedRow: React.FunctionComponent<{ ); const totalNumberOfLogEntries = useMemo( () => - results && results.histogramBuckets + results?.histogramBuckets ? getTotalNumberOfLogEntriesForPartition(results, partitionId) : undefined, [results, partitionId] diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/anomalies/index.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/index.tsx similarity index 97% rename from x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/anomalies/index.tsx rename to x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/index.tsx index e870c2d44271..38aa4b068c9e 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/anomalies/index.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/index.tsx @@ -18,7 +18,7 @@ import { i18n } from '@kbn/i18n'; import React, { useMemo } from 'react'; import euiStyled from '../../../../../../../../common/eui_styled_components'; -import { LogRateResults } from '../../../../../containers/logs/log_analysis/log_analysis_results'; +import { LogEntryRateResults } from '../../use_log_entry_rate_results'; import { TimeRange } from '../../../../../../common/http_api/shared/time_range'; import { JobStatus, SetupStatus } from '../../../../../../common/log_analysis'; import { @@ -30,13 +30,13 @@ import { import { AnomaliesChart } from './chart'; import { AnomaliesTable } from './table'; import { LogAnalysisJobProblemIndicator } from '../../../../../components/logging/log_analysis_job_status'; -import { AnalyzeInMlButton } from '../analyze_in_ml_button'; +import { AnalyzeInMlButton } from '../../../../../components/logging/log_analysis_results'; import { LoadingOverlayWrapper } from '../../../../../components/loading_overlay_wrapper'; export const AnomaliesResults: React.FunctionComponent<{ isLoading: boolean; jobStatus: JobStatus; - results: LogRateResults | null; + results: LogEntryRateResults | null; setTimeRange: (timeRange: TimeRange) => void; setupStatus: SetupStatus; timeRange: TimeRange; diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/anomalies/table.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/table.tsx similarity index 95% rename from x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/anomalies/table.tsx rename to x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/table.tsx index c0016d07c290..86760cf2da7d 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/anomalies/table.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/table.tsx @@ -9,7 +9,7 @@ import { EuiBasicTable, EuiButtonIcon } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { RIGHT_ALIGNMENT } from '@elastic/eui/lib/services'; import { TimeRange } from '../../../../../../common/http_api/shared/time_range'; -import { LogRateResults } from '../../../../../containers/logs/log_analysis/log_analysis_results'; +import { LogEntryRateResults } from '../../use_log_entry_rate_results'; import { AnomaliesTableExpandedRow } from './expanded_row'; import { formatAnomalyScore, getFriendlyNameForPartitionId } from '../helpers/data_formatters'; import euiStyled from '../../../../../../../../common/eui_styled_components'; @@ -50,7 +50,7 @@ const maxAnomalyScoreColumnName = i18n.translate( ); export const AnomaliesTable: React.FunctionComponent<{ - results: LogRateResults; + results: LogEntryRateResults; setTimeRange: (timeRange: TimeRange) => void; timeRange: TimeRange; jobId: string; @@ -124,7 +124,7 @@ export const AnomaliesTable: React.FunctionComponent<{ setItemIdToExpandedRowMap(newItemIdToExpandedRowMap); } }, - [results, setTimeRange, timeRange, itemIdToExpandedRowMap, setItemIdToExpandedRowMap] + [itemIdToExpandedRowMap, jobId, results, setTimeRange, timeRange] ); const columns = [ diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/helpers/data_formatters.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/sections/helpers/data_formatters.tsx similarity index 88% rename from x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/helpers/data_formatters.tsx rename to x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/sections/helpers/data_formatters.tsx index 74a3b5f80a57..f9b85fc4e20c 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/helpers/data_formatters.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/sections/helpers/data_formatters.tsx @@ -6,18 +6,19 @@ import { RectAnnotationDatum } from '@elastic/charts'; import { i18n } from '@kbn/i18n'; -import { LogRateResults } from '../../../../../containers/logs/log_analysis/log_analysis_results'; -export type MLSeverityScoreCategories = 'warning' | 'minor' | 'major' | 'critical'; -type MLSeverityScores = Record; -const ML_SEVERITY_SCORES: MLSeverityScores = { +import { LogEntryRateResults } from '../../use_log_entry_rate_results'; + +const ML_SEVERITY_SCORES = { warning: 3, minor: 25, major: 50, critical: 75, }; -export const getLogEntryRatePartitionedSeries = (results: LogRateResults) => { +export type MLSeverityScoreCategories = keyof typeof ML_SEVERITY_SCORES; + +export const getLogEntryRatePartitionedSeries = (results: LogEntryRateResults) => { return results.histogramBuckets.reduce>( (buckets, bucket) => { return [ @@ -33,7 +34,7 @@ export const getLogEntryRatePartitionedSeries = (results: LogRateResults) => { ); }; -export const getLogEntryRateCombinedSeries = (results: LogRateResults) => { +export const getLogEntryRateCombinedSeries = (results: LogEntryRateResults) => { return results.histogramBuckets.reduce>( (buckets, bucket) => { return [ @@ -50,7 +51,10 @@ export const getLogEntryRateCombinedSeries = (results: LogRateResults) => { ); }; -export const getLogEntryRateSeriesForPartition = (results: LogRateResults, partitionId: string) => { +export const getLogEntryRateSeriesForPartition = ( + results: LogEntryRateResults, + partitionId: string +) => { return results.partitionBuckets[partitionId].buckets.reduce< Array<{ time: number; value: number }> >((buckets, bucket) => { @@ -64,7 +68,7 @@ export const getLogEntryRateSeriesForPartition = (results: LogRateResults, parti }, []); }; -export const getAnnotationsForPartition = (results: LogRateResults, partitionId: string) => { +export const getAnnotationsForPartition = (results: LogEntryRateResults, partitionId: string) => { return results.partitionBuckets[partitionId].buckets.reduce< Record >( @@ -106,13 +110,13 @@ export const getAnnotationsForPartition = (results: LogRateResults, partitionId: }; export const getTotalNumberOfLogEntriesForPartition = ( - results: LogRateResults, + results: LogEntryRateResults, partitionId: string ) => { return results.partitionBuckets[partitionId].totalNumberOfLogEntries; }; -export const getAnnotationsForAll = (results: LogRateResults) => { +export const getAnnotationsForAll = (results: LogEntryRateResults) => { return results.histogramBuckets.reduce>( (annotatedBucketsBySeverity, bucket) => { const maxAnomalyScoresByPartition = bucket.partitions.reduce< @@ -169,7 +173,7 @@ export const getAnnotationsForAll = (results: LogRateResults) => { ); }; -export const getTopAnomalyScoreAcrossAllPartitions = (results: LogRateResults) => { +export const getTopAnomalyScoreAcrossAllPartitions = (results: LogEntryRateResults) => { const allTopScores = Object.values(results.partitionBuckets).reduce( (scores: number[], partition) => { return [...scores, partition.topAnomalyScore]; diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate/bar_chart.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/sections/log_rate/bar_chart.tsx similarity index 100% rename from x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate/bar_chart.tsx rename to x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/sections/log_rate/bar_chart.tsx diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate/index.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/sections/log_rate/index.tsx similarity index 96% rename from x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate/index.tsx rename to x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/sections/log_rate/index.tsx index 44805520f3b9..a11dc9d4d607 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate/index.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/sections/log_rate/index.tsx @@ -8,7 +8,7 @@ import { EuiEmptyPrompt, EuiLoadingSpinner, EuiSpacer, EuiTitle, EuiText } from import { i18n } from '@kbn/i18n'; import React, { useMemo } from 'react'; -import { LogRateResults as Results } from '../../../../../containers/logs/log_analysis/log_analysis_results'; +import { LogEntryRateResults as Results } from '../../use_log_entry_rate_results'; import { TimeRange } from '../../../../../../common/http_api/shared/time_range'; import { LogEntryRateBarChart } from './bar_chart'; import { getLogEntryRatePartitionedSeries } from '../helpers/data_formatters'; diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/get_log_entry_rate.ts b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/service_calls/get_log_entry_rate.ts similarity index 100% rename from x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/get_log_entry_rate.ts rename to x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/service_calls/get_log_entry_rate.ts diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/setup/index.ts b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/setup/index.ts similarity index 100% rename from x-pack/legacy/plugins/infra/public/pages/logs/analysis/setup/index.ts rename to x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/setup/index.ts diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/setup/initial_configuration_step/analysis_setup_indices_form.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/setup/initial_configuration_step/analysis_setup_indices_form.tsx similarity index 87% rename from x-pack/legacy/plugins/infra/public/pages/logs/analysis/setup/initial_configuration_step/analysis_setup_indices_form.tsx rename to x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/setup/initial_configuration_step/analysis_setup_indices_form.tsx index 585a65b9ad1c..5a4c21670191 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/setup/initial_configuration_step/analysis_setup_indices_form.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/setup/initial_configuration_step/analysis_setup_indices_form.tsx @@ -25,7 +25,7 @@ export const AnalysisSetupIndicesForm: React.FunctionComponent<{ onChangeSelectedIndices( indices.map(index => { const checkbox = event.currentTarget; - return index.index === checkbox.id ? { ...index, isSelected: checkbox.checked } : index; + return index.name === checkbox.id ? { ...index, isSelected: checkbox.checked } : index; }) ); }, @@ -35,37 +35,38 @@ export const AnalysisSetupIndicesForm: React.FunctionComponent<{ const choices = useMemo( () => indices.map(index => { - const validIndex = index.errors.length === 0; const checkbox = ( {index.index}} + key={index.name} + id={index.name} + label={{index.name}} onChange={handleCheckboxChange} - checked={index.isSelected} - disabled={!validIndex} + checked={index.validity === 'valid' && index.isSelected} + disabled={index.validity === 'invalid'} /> ); - return validIndex ? ( + return index.validity === 'valid' ? ( checkbox ) : ( -
+
{checkbox}
); }), - [indices] + [handleCheckboxChange, indices] ); return ( +

+ +

} description={ +

+ +

} description={ void; -interface AnalysisSetupStepsProps { - availableIndices: string[]; +interface LogEntryRateSetupStepsProps { cleanupAndSetup: SetupHandler; errorMessages: string[]; setup: SetupHandler; setupStatus: SetupStatus; - timestampField: string; viewResults: () => void; + moduleDescriptor: ModuleDescriptor; + sourceConfiguration: ModuleSourceConfiguration; } -export const AnalysisSetupSteps: React.FunctionComponent = ({ - availableIndices, +export const LogEntryRateSetupSteps = ({ cleanupAndSetup: cleanupAndSetupModule, errorMessages, setup: setupModule, setupStatus, - timestampField, viewResults, -}: AnalysisSetupStepsProps) => { + moduleDescriptor, + sourceConfiguration, +}: LogEntryRateSetupStepsProps) => { const { setup, cleanupAndSetup, @@ -50,10 +54,10 @@ export const AnalysisSetupSteps: React.FunctionComponent { + const sourceConfiguration: ModuleSourceConfiguration = useMemo( + () => ({ + indices: indexPattern.split(','), + sourceId, + spaceId, + timestampField, + }), + [indexPattern, sourceId, spaceId, timestampField] + ); + + return useLogAnalysisModule({ + moduleDescriptor: logEntryRateModule, + sourceConfiguration, + }); +}; + +export const [LogEntryRateModuleProvider, useLogEntryRateModuleContext] = createContainer( + useLogEntryRateModule +); diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_results.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/use_log_entry_rate_results.ts similarity index 58% rename from x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_results.tsx rename to x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/use_log_entry_rate_results.ts index 81a80fb565a4..de2b873001cc 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_results.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/use_log_entry_rate_results.ts @@ -4,75 +4,77 @@ * you may not use this file except in compliance with the Elastic License. */ -import createContainer from 'constate'; -import { useMemo, useEffect } from 'react'; +import { useMemo, useState } from 'react'; -import { useLogEntryRate } from './log_entry_rate'; -import { GetLogEntryRateSuccessResponsePayload } from '../../../../common/http_api/log_analysis'; +import { + GetLogEntryRateSuccessResponsePayload, + LogEntryRateHistogramBucket, + LogEntryRatePartition, +} from '../../../../common/http_api/log_analysis'; +import { useTrackedPromise } from '../../../utils/use_tracked_promise'; +import { callGetLogEntryRateAPI } from './service_calls/get_log_entry_rate'; -type PartitionBucket = { +type PartitionBucket = LogEntryRatePartition & { startTime: number; -} & GetLogEntryRateSuccessResponsePayload['data']['histogramBuckets'][0]['partitions'][0]; +}; type PartitionRecord = Record< string, { buckets: PartitionBucket[]; topAnomalyScore: number; totalNumberOfLogEntries: number } >; -export interface LogRateResults { +export interface LogEntryRateResults { bucketDuration: number; totalNumberOfLogEntries: number; - histogramBuckets: GetLogEntryRateSuccessResponsePayload['data']['histogramBuckets']; + histogramBuckets: LogEntryRateHistogramBucket[]; partitionBuckets: PartitionRecord; } -export const useLogAnalysisResults = ({ +export const useLogEntryRateResults = ({ sourceId, startTime, endTime, bucketDuration = 15 * 60 * 1000, - lastRequestTime, }: { sourceId: string; startTime: number; endTime: number; - bucketDuration?: number; - lastRequestTime: number; + bucketDuration: number; }) => { - const { isLoading: isLoadingLogEntryRate, logEntryRate, getLogEntryRate } = useLogEntryRate({ - sourceId, - startTime, - endTime, - bucketDuration, - }); - - const isLoading = useMemo(() => isLoadingLogEntryRate, [isLoadingLogEntryRate]); + const [logEntryRate, setLogEntryRate] = useState(null); - useEffect(() => { - getLogEntryRate(); - }, [sourceId, startTime, endTime, bucketDuration, lastRequestTime]); + const [getLogEntryRateRequest, getLogEntryRate] = useTrackedPromise( + { + cancelPreviousOn: 'resolution', + createPromise: async () => { + return await callGetLogEntryRateAPI(sourceId, startTime, endTime, bucketDuration); + }, + onResolve: ({ data }) => { + setLogEntryRate({ + bucketDuration: data.bucketDuration, + totalNumberOfLogEntries: data.totalNumberOfLogEntries, + histogramBuckets: data.histogramBuckets, + partitionBuckets: formatLogEntryRateResultsByPartition(data), + }); + }, + onReject: () => { + setLogEntryRate(null); + }, + }, + [sourceId, startTime, endTime, bucketDuration] + ); - const logRateResults: LogRateResults | null = useMemo(() => { - if (logEntryRate) { - return { - bucketDuration: logEntryRate.bucketDuration, - totalNumberOfLogEntries: logEntryRate.totalNumberOfLogEntries, - histogramBuckets: logEntryRate.histogramBuckets, - partitionBuckets: formatLogEntryRateResultsByPartition(logEntryRate), - }; - } else { - return null; - } - }, [logEntryRate]); + const isLoading = useMemo(() => getLogEntryRateRequest.state === 'pending', [ + getLogEntryRateRequest.state, + ]); return { + getLogEntryRate, isLoading, - logRateResults, + logEntryRate, }; }; -export const LogAnalysisResults = createContainer(useLogAnalysisResults); - const formatLogEntryRateResultsByPartition = ( results: GetLogEntryRateSuccessResponsePayload['data'] ): PartitionRecord => { diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_results_url_state.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/use_log_entry_rate_results_url_state.tsx similarity index 88% rename from x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_results_url_state.tsx rename to x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/use_log_entry_rate_results_url_state.tsx index 19fb7f238fc0..6d4495c8d9e0 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_results_url_state.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/use_log_entry_rate_results_url_state.tsx @@ -4,11 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { useEffect } from 'react'; -import * as rt from 'io-ts'; -import { identity, constant } from 'fp-ts/lib/function'; import { fold } from 'fp-ts/lib/Either'; +import { constant, identity } from 'fp-ts/lib/function'; import { pipe } from 'fp-ts/lib/pipeable'; +import * as rt from 'io-ts'; + import { useUrlState } from '../../../utils/use_url_state'; const autoRefreshRT = rt.union([ @@ -40,12 +40,9 @@ export const useLogAnalysisResultsUrlState = () => { pipe(urlTimeRangeRT.decode(value), fold(constant(undefined), identity)), encodeUrlState: urlTimeRangeRT.encode, urlStateKey: TIME_RANGE_URL_STATE_KEY, + writeDefaultState: true, }); - useEffect(() => { - setTimeRange(timeRange); - }, []); - const [autoRefresh, setAutoRefresh] = useUrlState({ defaultState: { isPaused: false, @@ -55,12 +52,9 @@ export const useLogAnalysisResultsUrlState = () => { pipe(autoRefreshRT.decode(value), fold(constant(undefined), identity)), encodeUrlState: autoRefreshRT.encode, urlStateKey: AUTOREFRESH_URL_STATE_KEY, + writeDefaultState: true, }); - useEffect(() => { - setAutoRefresh(autoRefresh); - }, []); - return { timeRange, setTimeRange, diff --git a/x-pack/legacy/plugins/infra/public/pages/metrics/components/chart_section_vis.tsx b/x-pack/legacy/plugins/infra/public/pages/metrics/components/chart_section_vis.tsx index 425b5a43f793..309961cc3902 100644 --- a/x-pack/legacy/plugins/infra/public/pages/metrics/components/chart_section_vis.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/metrics/components/chart_section_vis.tsx @@ -3,7 +3,7 @@ * 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, { useCallback } from 'react'; +import React, { useCallback, useMemo } from 'react'; import moment from 'moment'; import { i18n } from '@kbn/i18n'; import { @@ -42,15 +42,15 @@ export const ChartSectionVis = ({ seriesOverrides, type, }: VisSectionProps) => { - if (!metric || !id) { - return null; - } const [dateFormat] = useKibanaUiSetting('dateFormat'); const valueFormatter = useCallback(getFormatter(formatter, formatterTemplate), [ formatter, formatterTemplate, ]); - const dateFormatter = useCallback(niceTimeFormatter(getMaxMinTimestamp(metric)), [metric]); + const dateFormatter = useMemo( + () => (metric != null ? niceTimeFormatter(getMaxMinTimestamp(metric)) : undefined), + [metric] + ); const handleTimeChange = useCallback( (from: number, to: number) => { if (onChangeRangeTime) { @@ -73,7 +73,9 @@ export const ChartSectionVis = ({ ), }; - if (!metric) { + if (!id) { + return null; + } else if (!metric) { return ( ); - } - - if (metric.series.some(seriesHasLessThen2DataPoints)) { + } else if (metric.series.some(seriesHasLessThen2DataPoints)) { return ( { - if (!props.metadata) { - return null; - } - const { parsedTimeRange } = props; const { metrics, loading, makeRequest, error } = useNodeDetails( props.requiredMetrics, @@ -65,11 +61,11 @@ export const NodeDetailsPage = (props: Props) => { const refetch = useCallback(() => { makeRequest(); - }, []); + }, [makeRequest]); useEffect(() => { makeRequest(); - }, [parsedTimeRange]); + }, [makeRequest, parsedTimeRange]); if (error) { return ; diff --git a/x-pack/legacy/plugins/infra/public/pages/metrics/components/section.tsx b/x-pack/legacy/plugins/infra/public/pages/metrics/components/section.tsx index 32d2e2eff8ab..2f9ed9f54df8 100644 --- a/x-pack/legacy/plugins/infra/public/pages/metrics/components/section.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/metrics/components/section.tsx @@ -4,15 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ +import { EuiTitle } from '@elastic/eui'; import React, { - useContext, Children, - isValidElement, cloneElement, FunctionComponent, - useMemo, + isValidElement, + useContext, } from 'react'; -import { EuiTitle } from '@elastic/eui'; + import { SideNavContext, SubNavItem } from '../lib/side_nav_context'; import { LayoutProps } from '../types'; @@ -31,35 +31,42 @@ export const Section: FunctionComponent = ({ stopLiveStreaming, }) => { const { addNavItem } = useContext(SideNavContext); - const subNavItems: SubNavItem[] = []; - const childrenWithProps = useMemo( - () => - Children.map(children, child => { - if (isValidElement(child)) { - const metric = (metrics && metrics.find(m => m.id === child.props.id)) || null; - if (metric) { - subNavItems.push({ - id: child.props.id, - name: child.props.label, - onClick: () => { - const el = document.getElementById(child.props.id); - if (el) { - el.scrollIntoView(); - } - }, - }); - } - return cloneElement(child, { - metrics, - onChangeRangeTime, - isLiveStreaming, - stopLiveStreaming, - }); - } - return null; - }), - [children, metrics, onChangeRangeTime, isLiveStreaming, stopLiveStreaming] + const subNavItems = Children.toArray(children).reduce( + (accumulatedChildren, child) => { + if (!isValidElement(child)) { + return accumulatedChildren; + } + const metric = metrics?.find(m => m.id === child.props.id) ?? null; + if (metric === null) { + return accumulatedChildren; + } + return [ + ...accumulatedChildren, + { + id: child.props.id, + name: child.props.label, + onClick: () => { + const el = document.getElementById(child.props.id); + if (el) { + el.scrollIntoView(); + } + }, + }, + ]; + }, + [] + ); + + const childrenWithProps = Children.map(children, child => + isValidElement(child) + ? cloneElement(child, { + metrics, + onChangeRangeTime, + isLiveStreaming, + stopLiveStreaming, + }) + : null ); if (metrics && subNavItems.length) { diff --git a/x-pack/legacy/plugins/infra/public/pages/metrics/components/sub_section.tsx b/x-pack/legacy/plugins/infra/public/pages/metrics/components/sub_section.tsx index f3db3b167019..325d51029313 100644 --- a/x-pack/legacy/plugins/infra/public/pages/metrics/components/sub_section.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/metrics/components/sub_section.tsx @@ -23,29 +23,25 @@ export const SubSection: FunctionComponent = ({ isLiveStreaming, stopLiveStreaming, }) => { - if (!children || !metrics) { + const metric = useMemo(() => metrics?.find(m => m.id === id), [id, metrics]); + + if (!children || !metric) { return null; } - const metric = metrics.find(m => m.id === id); - if (!metric) { + + const childrenWithProps = Children.map(children, child => { + if (isValidElement(child)) { + return cloneElement(child, { + metric, + id, + onChangeRangeTime, + isLiveStreaming, + stopLiveStreaming, + }); + } return null; - } - const childrenWithProps = useMemo( - () => - Children.map(children, child => { - if (isValidElement(child)) { - return cloneElement(child, { - metric, - id, - onChangeRangeTime, - isLiveStreaming, - stopLiveStreaming, - }); - } - return null; - }), - [children, metric, id, onChangeRangeTime, isLiveStreaming, stopLiveStreaming] - ); + }); + return (
{label ? ( diff --git a/x-pack/legacy/plugins/infra/public/pages/metrics/containers/with_metrics_time.tsx b/x-pack/legacy/plugins/infra/public/pages/metrics/containers/with_metrics_time.tsx index 432725b6f62b..64d2ddb67139 100644 --- a/x-pack/legacy/plugins/infra/public/pages/metrics/containers/with_metrics_time.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/metrics/containers/with_metrics_time.tsx @@ -59,13 +59,10 @@ export const useMetricsTime = () => { const [parsedTimeRange, setParsedTimeRange] = useState(parseRange(defaultRange)); - const updateTimeRange = useCallback( - (range: MetricsTimeInput) => { - setTimeRange(range); - setParsedTimeRange(parseRange(range)); - }, - [setParsedTimeRange] - ); + const updateTimeRange = useCallback((range: MetricsTimeInput) => { + setTimeRange(range); + setParsedTimeRange(parseRange(range)); + }, []); return { timeRange, diff --git a/x-pack/legacy/plugins/infra/public/pages/metrics/index.tsx b/x-pack/legacy/plugins/infra/public/pages/metrics/index.tsx index 93253406aec2..b330ad02f102 100644 --- a/x-pack/legacy/plugins/infra/public/pages/metrics/index.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/metrics/index.tsx @@ -112,26 +112,28 @@ export const MetricDetail = withMetricPageProviders( })} /> - + {metadata ? ( + + ) : null} )} diff --git a/x-pack/legacy/plugins/infra/public/utils/cancellable_effect.ts b/x-pack/legacy/plugins/infra/public/utils/cancellable_effect.ts index bb7d253ea155..a986af07f0c9 100644 --- a/x-pack/legacy/plugins/infra/public/utils/cancellable_effect.ts +++ b/x-pack/legacy/plugins/infra/public/utils/cancellable_effect.ts @@ -27,5 +27,8 @@ export const useCancellableEffect = ( effect(() => cancellationSignal.isCancelled); return cancellationSignal.cancel; + + // the dependencies are managed externally + // eslint-disable-next-line react-hooks/exhaustive-deps }, deps); }; diff --git a/x-pack/legacy/plugins/infra/public/utils/use_kibana_ui_setting.ts b/x-pack/legacy/plugins/infra/public/utils/use_kibana_ui_setting.ts index c48f95a6521c..1b08fb423124 100644 --- a/x-pack/legacy/plugins/infra/public/utils/use_kibana_ui_setting.ts +++ b/x-pack/legacy/plugins/infra/public/utils/use_kibana_ui_setting.ts @@ -28,10 +28,15 @@ import { useObservable } from './use_observable'; export const useKibanaUiSetting = (key: string, defaultValue?: any) => { const uiSettingsClient = npSetup.core.uiSettings; - const uiSetting$ = useMemo(() => uiSettingsClient.get$(key, defaultValue), [uiSettingsClient]); + const uiSetting$ = useMemo(() => uiSettingsClient.get$(key, defaultValue), [ + defaultValue, + key, + uiSettingsClient, + ]); const uiSetting = useObservable(uiSetting$); const setUiSetting = useCallback((value: any) => uiSettingsClient.set(key, value), [ + key, uiSettingsClient, ]); diff --git a/x-pack/legacy/plugins/infra/public/utils/use_tracked_promise.ts b/x-pack/legacy/plugins/infra/public/utils/use_tracked_promise.ts index 366caf0dfb15..c23bab7026aa 100644 --- a/x-pack/legacy/plugins/infra/public/utils/use_tracked_promise.ts +++ b/x-pack/legacy/plugins/infra/public/utils/use_tracked_promise.ts @@ -190,6 +190,8 @@ export const useTrackedPromise = ( return newPendingPromise.promise; }, + // the dependencies are managed by the caller + // eslint-disable-next-line react-hooks/exhaustive-deps dependencies ); diff --git a/x-pack/legacy/plugins/infra/public/utils/use_url_state.ts b/x-pack/legacy/plugins/infra/public/utils/use_url_state.ts index d03a5aaa9d69..79a5d552bcd7 100644 --- a/x-pack/legacy/plugins/infra/public/utils/use_url_state.ts +++ b/x-pack/legacy/plugins/infra/public/utils/use_url_state.ts @@ -5,10 +5,10 @@ */ import { Location } from 'history'; -import { useMemo, useCallback } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import { decode, encode, RisonValue } from 'rison-node'; - import { QueryString } from 'ui/utils/query_string'; + import { useHistory } from './history_context'; export const useUrlState = ({ @@ -16,21 +16,26 @@ export const useUrlState = ({ decodeUrlState, encodeUrlState, urlStateKey, + writeDefaultState = false, }: { defaultState: State; decodeUrlState: (value: RisonValue | undefined) => State | undefined; encodeUrlState: (value: State) => RisonValue | undefined; urlStateKey: string; + writeDefaultState?: boolean; }) => { const history = useHistory(); + // history.location is mutable so we can't reliably use useMemo + const queryString = history?.location ? getQueryStringFromLocation(history.location) : ''; + const urlStateString = useMemo(() => { - if (!history) { + if (!queryString) { return; } - return getParamFromQueryString(getQueryStringFromLocation(history.location), urlStateKey); - }, [history && history.location, urlStateKey]); + return getParamFromQueryString(queryString, urlStateKey); + }, [queryString, urlStateKey]); const decodedState = useMemo(() => decodeUrlState(decodeRisonUrlState(urlStateString)), [ decodeUrlState, @@ -44,27 +49,38 @@ export const useUrlState = ({ const setState = useCallback( (newState: State | undefined) => { - if (!history) { + if (!history || !history.location) { return; } - const location = history.location; + const currentLocation = history.location; const newLocation = replaceQueryStringInLocation( - location, + currentLocation, replaceStateKeyInQueryString( urlStateKey, typeof newState !== 'undefined' ? encodeUrlState(newState) : undefined - )(getQueryStringFromLocation(location)) + )(getQueryStringFromLocation(currentLocation)) ); - if (newLocation !== location) { + if (newLocation !== currentLocation) { history.replace(newLocation); } }, - [encodeUrlState, history, history && history.location, urlStateKey] + [encodeUrlState, history, urlStateKey] ); + const [shouldInitialize, setShouldInitialize] = useState( + writeDefaultState && typeof decodedState === 'undefined' + ); + + useEffect(() => { + if (shouldInitialize) { + setShouldInitialize(false); + setState(defaultState); + } + }, [shouldInitialize, setState, defaultState]); + return [state, setState] as [typeof state, typeof setState]; }; diff --git a/x-pack/legacy/plugins/infra/public/utils/use_visibility_state.ts b/x-pack/legacy/plugins/infra/public/utils/use_visibility_state.ts index 5763834b1cc2..f4d8b572e4f7 100644 --- a/x-pack/legacy/plugins/infra/public/utils/use_visibility_state.ts +++ b/x-pack/legacy/plugins/infra/public/utils/use_visibility_state.ts @@ -20,6 +20,6 @@ export const useVisibilityState = (initialState: boolean) => { show, toggle, }), - [isVisible, show, hide] + [hide, isVisible, show, toggle] ); }; diff --git a/x-pack/legacy/plugins/infra/server/infra_server.ts b/x-pack/legacy/plugins/infra/server/infra_server.ts index 845e54e18c7c..e0c8f607daa9 100644 --- a/x-pack/legacy/plugins/infra/server/infra_server.ts +++ b/x-pack/legacy/plugins/infra/server/infra_server.ts @@ -12,8 +12,8 @@ import { createSourceStatusResolvers } from './graphql/source_status'; import { createSourcesResolvers } from './graphql/sources'; import { InfraBackendLibs } from './lib/infra_types'; import { - initLogAnalysisGetLogEntryRateRoute, - initIndexPatternsValidateRoute, + initGetLogEntryRateRoute, + initValidateLogAnalysisIndicesRoute, } from './routes/log_analysis'; import { initMetricExplorerRoute } from './routes/metrics_explorer'; import { initMetadataRoute } from './routes/metadata'; @@ -33,10 +33,10 @@ export const initInfraServer = (libs: InfraBackendLibs) => { libs.framework.registerGraphQLEndpoint('/graphql', schema); initIpToHostName(libs); - initLogAnalysisGetLogEntryRateRoute(libs); + initGetLogEntryRateRoute(libs); initSnapshotRoute(libs); initNodeDetailsRoute(libs); - initIndexPatternsValidateRoute(libs); + initValidateLogAnalysisIndicesRoute(libs); initMetricExplorerRoute(libs); initMetadataRoute(libs); }; diff --git a/x-pack/legacy/plugins/infra/server/lib/adapters/framework/adapter_types.ts b/x-pack/legacy/plugins/infra/server/lib/adapters/framework/adapter_types.ts index 625607c09802..e88736b08b95 100644 --- a/x-pack/legacy/plugins/infra/server/lib/adapters/framework/adapter_types.ts +++ b/x-pack/legacy/plugins/infra/server/lib/adapters/framework/adapter_types.ts @@ -29,12 +29,12 @@ export interface InfraServerPluginDeps { export interface CallWithRequestParams extends GenericParams { max_concurrent_shard_requests?: number; name?: string; - index?: string; + index?: string | string[]; ignore_unavailable?: boolean; allow_no_indices?: boolean; size?: number; terminate_after?: number; - fields?: string; + fields?: string | string[]; } export type InfraResponse = Lifecycle.ReturnValue; diff --git a/x-pack/legacy/plugins/infra/server/routes/log_analysis/index.ts b/x-pack/legacy/plugins/infra/server/routes/log_analysis/index.ts index 7364d167efe4..378e32cb3582 100644 --- a/x-pack/legacy/plugins/infra/server/routes/log_analysis/index.ts +++ b/x-pack/legacy/plugins/infra/server/routes/log_analysis/index.ts @@ -5,4 +5,4 @@ */ export * from './results'; -export * from './index_patterns'; +export * from './validation'; diff --git a/x-pack/legacy/plugins/infra/server/routes/log_analysis/results/log_entry_rate.ts b/x-pack/legacy/plugins/infra/server/routes/log_analysis/results/log_entry_rate.ts index 973080c880e6..02866e797e30 100644 --- a/x-pack/legacy/plugins/infra/server/routes/log_analysis/results/log_entry_rate.ts +++ b/x-pack/legacy/plugins/infra/server/routes/log_analysis/results/log_entry_rate.ts @@ -22,10 +22,7 @@ import { NoLogRateResultsIndexError } from '../../../lib/log_analysis'; const anyObject = schema.object({}, { allowUnknowns: true }); -export const initLogAnalysisGetLogEntryRateRoute = ({ - framework, - logAnalysis, -}: InfraBackendLibs) => { +export const initGetLogEntryRateRoute = ({ framework, logAnalysis }: InfraBackendLibs) => { framework.registerRoute( { method: 'post', diff --git a/x-pack/legacy/plugins/infra/server/routes/log_analysis/index_patterns/index.ts b/x-pack/legacy/plugins/infra/server/routes/log_analysis/validation/index.ts similarity index 89% rename from x-pack/legacy/plugins/infra/server/routes/log_analysis/index_patterns/index.ts rename to x-pack/legacy/plugins/infra/server/routes/log_analysis/validation/index.ts index a85e119e7318..727faca69298 100644 --- a/x-pack/legacy/plugins/infra/server/routes/log_analysis/index_patterns/index.ts +++ b/x-pack/legacy/plugins/infra/server/routes/log_analysis/validation/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export * from './validate'; +export * from './indices'; diff --git a/x-pack/legacy/plugins/infra/server/routes/log_analysis/index_patterns/validate.ts b/x-pack/legacy/plugins/infra/server/routes/log_analysis/validation/indices.ts similarity index 77% rename from x-pack/legacy/plugins/infra/server/routes/log_analysis/index_patterns/validate.ts rename to x-pack/legacy/plugins/infra/server/routes/log_analysis/validation/indices.ts index 1f64da1859b5..ba143a597b66 100644 --- a/x-pack/legacy/plugins/infra/server/routes/log_analysis/index_patterns/validate.ts +++ b/x-pack/legacy/plugins/infra/server/routes/log_analysis/validation/indices.ts @@ -11,7 +11,7 @@ import { identity } from 'fp-ts/lib/function'; import { schema } from '@kbn/config-schema'; import { InfraBackendLibs } from '../../../lib/infra_types'; import { - LOG_ANALYSIS_VALIDATION_INDICES_PATH, + LOG_ANALYSIS_VALIDATE_INDICES_PATH, validationIndicesRequestPayloadRT, validationIndicesResponsePayloadRT, ValidationIndicesError, @@ -19,14 +19,13 @@ import { import { throwErrors } from '../../../../common/runtime_types'; -const partitionField = 'event.dataset'; const escapeHatch = schema.object({}, { allowUnknowns: true }); -export const initIndexPatternsValidateRoute = ({ framework }: InfraBackendLibs) => { +export const initValidateLogAnalysisIndicesRoute = ({ framework }: InfraBackendLibs) => { framework.registerRoute( { method: 'post', - path: LOG_ANALYSIS_VALIDATION_INDICES_PATH, + path: LOG_ANALYSIS_VALIDATE_INDICES_PATH, validate: { body: escapeHatch }, }, async (requestContext, request, response) => { @@ -36,7 +35,7 @@ export const initIndexPatternsValidateRoute = ({ framework }: InfraBackendLibs) fold(throwErrors(Boom.badRequest), identity) ); - const { timestampField, indices } = payload.data; + const { fields, indices } = payload.data; const errors: ValidationIndicesError[] = []; // Query each pattern individually, to map correctly the errors @@ -44,7 +43,7 @@ export const initIndexPatternsValidateRoute = ({ framework }: InfraBackendLibs) indices.map(async index => { const fieldCaps = await framework.callWithRequest(requestContext, 'fieldCaps', { index, - fields: `${timestampField},${partitionField}`, + fields: fields.map(field => field.name), }); if (fieldCaps.indices.length === 0) { @@ -55,32 +54,30 @@ export const initIndexPatternsValidateRoute = ({ framework }: InfraBackendLibs) return; } - ([ - [timestampField, 'date'], - [partitionField, 'keyword'], - ] as const).forEach(([field, fieldType]) => { - const fieldMetadata = fieldCaps.fields[field]; + fields.forEach(({ name: fieldName, validTypes }) => { + const fieldMetadata = fieldCaps.fields[fieldName]; if (fieldMetadata === undefined) { errors.push({ error: 'FIELD_NOT_FOUND', index, - field, + field: fieldName, }); } else { const fieldTypes = Object.keys(fieldMetadata); - if (fieldTypes.length > 1 || fieldTypes[0] !== fieldType) { + if (!fieldTypes.every(fieldType => validTypes.includes(fieldType))) { errors.push({ error: `FIELD_NOT_VALID`, index, - field, + field: fieldName, }); } } }); }) ); + return response.ok({ body: validationIndicesResponsePayloadRT.encode({ data: { errors } }), }); diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/index_or_search/page.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/index_or_search/page.tsx index 68013bd243a9..cb311f04dd1d 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/index_or_search/page.tsx +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/index_or_search/page.tsx @@ -15,7 +15,8 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { SavedObjectFinder } from 'ui/saved_objects/components/saved_object_finder'; +import { npStart } from 'ui/new_platform'; +import { SavedObjectFinderUi } from '../../../../../../../../../../src/plugins/kibana_react/public'; export interface PageProps { nextStepPath: string; @@ -46,7 +47,7 @@ export const Page: FC = ({ nextStepPath }) => { - = ({ nextStepPath }) => { }, ]} fixedPageSize={RESULTS_PER_PAGE} + uiSettings={npStart.core.uiSettings} + savedObjects={npStart.core.savedObjects} /> diff --git a/x-pack/legacy/plugins/oss_telemetry/index.d.ts b/x-pack/legacy/plugins/oss_telemetry/index.d.ts deleted file mode 100644 index 1b592dabf205..000000000000 --- a/x-pack/legacy/plugins/oss_telemetry/index.d.ts +++ /dev/null @@ -1,61 +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. - */ - -export interface VisState { - type: string; -} - -export interface Visualization { - visState: string; -} - -export interface SavedObjectDoc { - _id: string; - _source: { - visualization: Visualization; - type: string; - }; -} - -export interface ESQueryResponse { - hits: { - hits: SavedObjectDoc[]; - }; -} - -export interface TaskInstance { - state: { - runs: number; - stats: any; - }; - error?: any; -} - -export interface HapiServer { - plugins: { - xpack_main: any; - elasticsearch: { - getCluster: ( - cluster: string - ) => { - callWithInternalUser: () => Promise; - }; - }; - task_manager: { - registerTaskDefinitions: (opts: any) => void; - ensureScheduled: (opts: any) => Promise; - fetch: ( - opts: any - ) => Promise<{ - docs: TaskInstance[]; - }>; - }; - }; - config: () => { - get: (prop: string) => any; - }; - log: (context: string[], message: string) => void; -} diff --git a/x-pack/legacy/plugins/oss_telemetry/index.js b/x-pack/legacy/plugins/oss_telemetry/index.js deleted file mode 100644 index f86baef020aa..000000000000 --- a/x-pack/legacy/plugins/oss_telemetry/index.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 { registerCollectors } from './server/lib/collectors'; -import { registerTasks, scheduleTasks } from './server/lib/tasks'; -import { PLUGIN_ID } from './constants'; - -export const ossTelemetry = (kibana) => { - return new kibana.Plugin({ - id: PLUGIN_ID, - require: ['elasticsearch', 'xpack_main'], - configPrefix: 'xpack.oss_telemetry', - - init(server) { - const { usageCollection } = server.newPlatform.setup.plugins; - registerCollectors(usageCollection, server); - registerTasks(server); - scheduleTasks(server); - } - }); -}; diff --git a/x-pack/legacy/plugins/oss_telemetry/index.ts b/x-pack/legacy/plugins/oss_telemetry/index.ts new file mode 100644 index 000000000000..8b16c7cf13ca --- /dev/null +++ b/x-pack/legacy/plugins/oss_telemetry/index.ts @@ -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 { Logger, PluginInitializerContext } from 'kibana/server'; +import { PLUGIN_ID } from './constants'; +import { OssTelemetryPlugin } from './server/plugin'; +import { LegacyPluginInitializer } from '../../../../src/legacy/plugin_discovery/types'; + +export const ossTelemetry: LegacyPluginInitializer = kibana => { + return new kibana.Plugin({ + id: PLUGIN_ID, + require: ['elasticsearch', 'xpack_main'], + configPrefix: 'xpack.oss_telemetry', + + init(server) { + const plugin = new OssTelemetryPlugin({ + logger: { + get: () => + ({ + info: (message: string) => server.log(['info', 'task_manager'], message), + debug: (message: string) => server.log(['debug', 'task_manager'], message), + warn: (message: string) => server.log(['warn', 'task_manager'], message), + error: (message: string) => server.log(['error', 'task_manager'], message), + } as Logger), + }, + } as PluginInitializerContext); + plugin.setup(server.newPlatform.setup.core, { + usageCollection: server.newPlatform.setup.plugins.usageCollection, + taskManager: server.plugins.task_manager, + __LEGACY: { + config: server.config(), + xpackMainStatus: ((server.plugins.xpack_main as unknown) as { status: any }).status + .plugin, + }, + }); + }, + }); +}; diff --git a/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/index.ts b/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/index.ts index 0121ed4304d2..3b47099fdc46 100644 --- a/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/index.ts +++ b/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/index.ts @@ -4,10 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; -import { HapiServer } from '../../../'; import { registerVisualizationsCollector } from './visualizations/register_usage_collector'; +import { OssTelemetrySetupDependencies } from '../../plugin'; -export function registerCollectors(usageCollection: UsageCollectionSetup, server: HapiServer) { - registerVisualizationsCollector(usageCollection, server); +export function registerCollectors(deps: OssTelemetrySetupDependencies) { + registerVisualizationsCollector(deps.usageCollection, deps.taskManager); } diff --git a/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/visualizations/get_usage_collector.test.ts b/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/visualizations/get_usage_collector.test.ts index d316562c826d..ec3526664665 100644 --- a/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/visualizations/get_usage_collector.test.ts +++ b/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/visualizations/get_usage_collector.test.ts @@ -4,24 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import sinon from 'sinon'; -import { HapiServer } from '../../../../'; -import { - getMockCallWithInternal, - getMockKbnServer, - getMockTaskFetch, -} from '../../../../test_utils'; +import { getMockTaskFetch, getMockTaskManager } from '../../../../test_utils'; import { getUsageCollector } from './get_usage_collector'; describe('getVisualizationsCollector#fetch', () => { - let mockKbnServer: HapiServer; - - beforeEach(() => { - mockKbnServer = getMockKbnServer(getMockCallWithInternal(), getMockTaskFetch()); - }); - test('can return empty stats', async () => { - const { type, fetch } = getUsageCollector(mockKbnServer); + const { type, fetch } = getUsageCollector(getMockTaskManager()); expect(type).toBe('visualization_types'); const fetchResult = await fetch(); expect(fetchResult).toEqual({}); @@ -34,11 +22,11 @@ describe('getVisualizationsCollector#fetch', () => { runs: 1, stats: { comic_books: { total: 16, max: 12, min: 2, avg: 6 } }, }, + taskType: 'test', + params: {}, }, ]); - mockKbnServer = getMockKbnServer(getMockCallWithInternal(), mockTaskFetch); - - const { type, fetch } = getUsageCollector(mockKbnServer); + const { type, fetch } = getUsageCollector(getMockTaskManager(mockTaskFetch)); expect(type).toBe('visualization_types'); const fetchResult = await fetch(); expect(fetchResult).toEqual({ comic_books: { avg: 6, max: 12, min: 2, total: 16 } }); @@ -46,23 +34,21 @@ describe('getVisualizationsCollector#fetch', () => { describe('Error handling', () => { test('Silently handles Task Manager NotInitialized', async () => { - const mockTaskFetch = sinon.stub(); - mockTaskFetch.rejects( - new Error('NotInitialized taskManager is still waiting for plugins to load') - ); - mockKbnServer = getMockKbnServer(getMockCallWithInternal(), mockTaskFetch); - - const { fetch } = getUsageCollector(mockKbnServer); - await expect(fetch()).resolves.toBe(undefined); + const mockTaskFetch = jest.fn(() => { + throw new Error('NotInitialized taskManager is still waiting for plugins to load'); + }); + const { fetch } = getUsageCollector(getMockTaskManager(mockTaskFetch)); + const result = await fetch(); + expect(result).toBe(undefined); }); // In real life, the CollectorSet calls fetch and handles errors test('defers the errors', async () => { - const mockTaskFetch = sinon.stub(); - mockTaskFetch.rejects(new Error('BOOM')); - mockKbnServer = getMockKbnServer(getMockCallWithInternal(), mockTaskFetch); + const mockTaskFetch = jest.fn(() => { + throw new Error('BOOM'); + }); - const { fetch } = getUsageCollector(mockKbnServer); - await expect(fetch()).rejects.toMatchObject(new Error('BOOM')); + const { fetch } = getUsageCollector(getMockTaskManager(mockTaskFetch)); + await expect(fetch()).rejects.toThrowErrorMatchingInlineSnapshot(`"BOOM"`); }); }); }); diff --git a/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/visualizations/get_usage_collector.ts b/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/visualizations/get_usage_collector.ts index 63640c87f80a..680cb97e0fda 100644 --- a/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/visualizations/get_usage_collector.ts +++ b/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/visualizations/get_usage_collector.ts @@ -5,17 +5,15 @@ */ import { get } from 'lodash'; -import { HapiServer } from '../../../../'; +import { PluginSetupContract as TaskManagerPluginSetupContract } from '../../../../../task_manager/plugin'; import { PLUGIN_ID, VIS_TELEMETRY_TASK, VIS_USAGE_TYPE } from '../../../../constants'; -async function isTaskManagerReady(server: HapiServer) { - const result = await fetch(server); +async function isTaskManagerReady(taskManager: TaskManagerPluginSetupContract | undefined) { + const result = await fetch(taskManager); return result !== null; } -async function fetch(server: HapiServer) { - const taskManager = server.plugins.task_manager; - +async function fetch(taskManager: TaskManagerPluginSetupContract | undefined) { if (!taskManager) { return null; } @@ -40,12 +38,12 @@ async function fetch(server: HapiServer) { return docs; } -export function getUsageCollector(server: HapiServer) { +export function getUsageCollector(taskManager: TaskManagerPluginSetupContract | undefined) { let isCollectorReady = false; async function determineIfTaskManagerIsReady() { let isReady = false; try { - isReady = await isTaskManagerReady(server); + isReady = await isTaskManagerReady(taskManager); } catch (err) {} // eslint-disable-line if (isReady) { @@ -60,7 +58,7 @@ export function getUsageCollector(server: HapiServer) { type: VIS_USAGE_TYPE, isReady: () => isCollectorReady, fetch: async () => { - const docs = await fetch(server); + const docs = await fetch(taskManager); // get the accumulated state from the recurring task return get(docs, '[0].state.stats'); }, diff --git a/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/visualizations/register_usage_collector.ts b/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/visualizations/register_usage_collector.ts index 09843a6f87ad..1a47f68adcc5 100644 --- a/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/visualizations/register_usage_collector.ts +++ b/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/visualizations/register_usage_collector.ts @@ -5,13 +5,13 @@ */ import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; -import { HapiServer } from '../../../../'; +import { PluginSetupContract as TaskManagerPluginSetupContract } from '../../../../../task_manager/plugin'; import { getUsageCollector } from './get_usage_collector'; export function registerVisualizationsCollector( - usageCollection: UsageCollectionSetup, - server: HapiServer + collectorSet: UsageCollectionSetup, + taskManager: TaskManagerPluginSetupContract | undefined ): void { - const collector = usageCollection.makeUsageCollector(getUsageCollector(server)); - usageCollection.registerCollector(collector); + const collector = collectorSet.makeUsageCollector(getUsageCollector(taskManager)); + collectorSet.registerCollector(collector); } diff --git a/x-pack/legacy/plugins/oss_telemetry/server/lib/tasks/index.ts b/x-pack/legacy/plugins/oss_telemetry/server/lib/tasks/index.ts index 16e83a7938e6..cb6b4eab0974 100644 --- a/x-pack/legacy/plugins/oss_telemetry/server/lib/tasks/index.ts +++ b/x-pack/legacy/plugins/oss_telemetry/server/lib/tasks/index.ts @@ -4,15 +4,27 @@ * you may not use this file except in compliance with the Elastic License. */ -import { HapiServer } from '../../../'; +import { CoreSetup, Logger } from 'kibana/server'; +import { PluginSetupContract as TaskManagerPluginSetupContract } from '../../../../task_manager/plugin'; import { PLUGIN_ID, VIS_TELEMETRY_TASK } from '../../../constants'; import { visualizationsTaskRunner } from './visualizations/task_runner'; +import KbnServer from '../../../../../../../src/legacy/server/kbn_server'; +import { LegacyConfig } from '../../plugin'; +import { TaskInstance } from '../../../../task_manager'; -export function registerTasks(server: HapiServer) { - const taskManager = server.plugins.task_manager; - +export function registerTasks({ + taskManager, + logger, + elasticsearch, + config, +}: { + taskManager?: TaskManagerPluginSetupContract; + logger: Logger; + elasticsearch: CoreSetup['elasticsearch']; + config: LegacyConfig; +}) { if (!taskManager) { - server.log(['debug', 'telemetry'], `Task manager is not available`); + logger.debug('Task manager is not available'); return; } @@ -20,18 +32,30 @@ export function registerTasks(server: HapiServer) { [VIS_TELEMETRY_TASK]: { title: 'X-Pack telemetry calculator for Visualizations', type: VIS_TELEMETRY_TASK, - createTaskRunner({ taskInstance }: { taskInstance: any }) { + createTaskRunner({ taskInstance }: { taskInstance: TaskInstance }) { return { - run: visualizationsTaskRunner(taskInstance, server), + run: visualizationsTaskRunner(taskInstance, config, elasticsearch), }; }, }, }); } -export function scheduleTasks(server: HapiServer) { - const taskManager = server.plugins.task_manager; - const { kbnServer } = server.plugins.xpack_main.status.plugin; +export function scheduleTasks({ + taskManager, + xpackMainStatus, + logger, +}: { + taskManager?: TaskManagerPluginSetupContract; + xpackMainStatus: { kbnServer: KbnServer }; + logger: Logger; +}) { + if (!taskManager) { + logger.debug('Task manager is not available'); + return; + } + + const { kbnServer } = xpackMainStatus; kbnServer.afterPluginsInit(() => { // The code block below can't await directly within "afterPluginsInit" @@ -46,9 +70,10 @@ export function scheduleTasks(server: HapiServer) { id: `${PLUGIN_ID}-${VIS_TELEMETRY_TASK}`, taskType: VIS_TELEMETRY_TASK, state: { stats: {}, runs: 0 }, + params: {}, }); } catch (e) { - server.log(['debug', 'telemetry'], `Error scheduling task, received ${e.message}`); + logger.debug(`Error scheduling task, received ${e.message}`); } })(); }); diff --git a/x-pack/legacy/plugins/oss_telemetry/server/lib/tasks/visualizations/task_runner.test.ts b/x-pack/legacy/plugins/oss_telemetry/server/lib/tasks/visualizations/task_runner.test.ts index 5db08ed291d6..0663a5bd330c 100644 --- a/x-pack/legacy/plugins/oss_telemetry/server/lib/tasks/visualizations/task_runner.test.ts +++ b/x-pack/legacy/plugins/oss_telemetry/server/lib/tasks/visualizations/task_runner.test.ts @@ -5,28 +5,30 @@ */ import moment from 'moment'; -import { HapiServer, TaskInstance } from '../../../../'; import { getMockCallWithInternal, - getMockKbnServer, + getMockConfig, + getMockEs, getMockTaskInstance, } from '../../../../test_utils'; import { visualizationsTaskRunner } from './task_runner'; +import { TaskInstance } from '../../../../../task_manager'; describe('visualizationsTaskRunner', () => { let mockTaskInstance: TaskInstance; - let mockKbnServer: HapiServer; beforeEach(() => { mockTaskInstance = getMockTaskInstance(); - mockKbnServer = getMockKbnServer(); }); describe('Error handling', () => { test('catches its own errors', async () => { const mockCallWithInternal = () => Promise.reject(new Error('Things did not go well!')); - mockKbnServer = getMockKbnServer(mockCallWithInternal); - const runner = visualizationsTaskRunner(mockTaskInstance, mockKbnServer); + const runner = visualizationsTaskRunner( + mockTaskInstance, + getMockConfig(), + getMockEs(mockCallWithInternal) + ); const result = await runner(); expect(result).toMatchObject({ error: 'Things did not go well!', @@ -45,7 +47,7 @@ describe('visualizationsTaskRunner', () => { .startOf('day') .toDate(); - const runner = visualizationsTaskRunner(mockTaskInstance, mockKbnServer); + const runner = visualizationsTaskRunner(mockTaskInstance, getMockConfig(), getMockEs()); const result = await runner(); expect(result).toMatchObject({ @@ -123,9 +125,12 @@ describe('visualizationsTaskRunner', () => { }, }, ]); - mockKbnServer = getMockKbnServer(mockCallWithInternal); - const runner = visualizationsTaskRunner(mockTaskInstance, mockKbnServer); + const runner = visualizationsTaskRunner( + mockTaskInstance, + getMockConfig(), + getMockEs(mockCallWithInternal) + ); const result = await runner(); expect(result).toMatchObject({ diff --git a/x-pack/legacy/plugins/oss_telemetry/server/lib/tasks/visualizations/task_runner.ts b/x-pack/legacy/plugins/oss_telemetry/server/lib/tasks/visualizations/task_runner.ts index 3372101c2b45..9d8f76f6a10d 100644 --- a/x-pack/legacy/plugins/oss_telemetry/server/lib/tasks/visualizations/task_runner.ts +++ b/x-pack/legacy/plugins/oss_telemetry/server/lib/tasks/visualizations/task_runner.ts @@ -5,15 +5,12 @@ */ import _, { countBy, groupBy, mapValues } from 'lodash'; -import { - ESQueryResponse, - HapiServer, - SavedObjectDoc, - TaskInstance, - VisState, - Visualization, -} from '../../../../'; +import { APICaller, CoreSetup } from 'kibana/server'; import { getNextMidnight } from '../../get_next_midnight'; +import { VisState } from '../../../../../../../../src/legacy/core_plugins/visualizations/public'; +import { TaskInstance } from '../../../../../task_manager'; +import { ESSearchHit } from '../../../../../apm/typings/elasticsearch'; +import { LegacyConfig } from '../../../plugin'; interface VisSummary { type: string; @@ -23,7 +20,7 @@ interface VisSummary { /* * Parse the response data into telemetry payload */ -async function getStats(callCluster: (method: string, params: any) => Promise, index: string) { +async function getStats(callCluster: APICaller, index: string) { const searchParams = { size: 10000, // elasticsearch index.max_result_window default value index, @@ -35,24 +32,26 @@ async function getStats(callCluster: (method: string, params: any) => Promise(esResponse, 'hits.hits.length'); if (size < 1) { return; } // `map` to get the raw types - const visSummaries: VisSummary[] = esResponse.hits.hits.map((hit: SavedObjectDoc) => { - const spacePhrases: string[] = hit._id.split(':'); - const space = spacePhrases.length === 3 ? spacePhrases[0] : 'default'; // if in a custom space, the format of a saved object ID is space:type:id - const visualization: Visualization = _.get(hit, '_source.visualization', { visState: '{}' }); - const visState: VisState = JSON.parse(visualization.visState); + const visSummaries: VisSummary[] = esResponse.hits.hits.map( + (hit: ESSearchHit<{ visState: string }>) => { + const spacePhrases: string[] = hit._id.split(':'); + const space = spacePhrases.length === 3 ? spacePhrases[0] : 'default'; // if in a custom space, the format of a saved object ID is space:type:id + const visualization = _.get(hit, '_source.visualization', { visState: '{}' }); + const visState: VisState = JSON.parse(visualization.visState); - return { - type: visState.type || '_na_', - space, - }; - }); + return { + type: visState.type || '_na_', + space, + }; + } + ); // organize stats per type const visTypes = groupBy(visSummaries, 'type'); @@ -72,9 +71,12 @@ async function getStats(callCluster: (method: string, params: any) => Promise { diff --git a/x-pack/legacy/plugins/oss_telemetry/server/plugin.ts b/x-pack/legacy/plugins/oss_telemetry/server/plugin.ts new file mode 100644 index 000000000000..f661311fc24b --- /dev/null +++ b/x-pack/legacy/plugins/oss_telemetry/server/plugin.ts @@ -0,0 +1,50 @@ +/* + * 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 { CoreSetup, Logger, Plugin, PluginInitializerContext } from 'kibana/server'; +import { PluginSetupContract as TaskManagerPluginSetupContract } from '../../task_manager/plugin'; +import { registerCollectors } from './lib/collectors'; +import { registerTasks, scheduleTasks } from './lib/tasks'; +import KbnServer from '../../../../../src/legacy/server/kbn_server'; +import { UsageCollectionSetup } from '../../../../../src/plugins/usage_collection/server'; + +export interface LegacyConfig { + get: (key: string) => string | number | boolean; +} + +export interface OssTelemetrySetupDependencies { + usageCollection: UsageCollectionSetup; + __LEGACY: { + config: LegacyConfig; + xpackMainStatus: { kbnServer: KbnServer }; + }; + taskManager?: TaskManagerPluginSetupContract; +} + +export class OssTelemetryPlugin implements Plugin { + private logger: Logger; + + constructor(initializerContext: PluginInitializerContext) { + this.logger = initializerContext.logger.get(); + } + + public setup(core: CoreSetup, deps: OssTelemetrySetupDependencies) { + registerCollectors(deps); + registerTasks({ + taskManager: deps.taskManager, + logger: this.logger, + elasticsearch: core.elasticsearch, + config: deps.__LEGACY.config, + }); + scheduleTasks({ + taskManager: deps.taskManager, + xpackMainStatus: deps.__LEGACY.xpackMainStatus, + logger: this.logger, + }); + } + + public start() {} +} diff --git a/x-pack/legacy/plugins/oss_telemetry/test_utils/index.ts b/x-pack/legacy/plugins/oss_telemetry/test_utils/index.ts index 1cebe78b9c7f..04e248d28b57 100644 --- a/x-pack/legacy/plugins/oss_telemetry/test_utils/index.ts +++ b/x-pack/legacy/plugins/oss_telemetry/test_utils/index.ts @@ -4,9 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ESQueryResponse, HapiServer, SavedObjectDoc, TaskInstance } from '../'; +import { APICaller, CoreSetup } from 'kibana/server'; -export const getMockTaskInstance = (): TaskInstance => ({ state: { runs: 0, stats: {} } }); +import { TaskInstance } from '../../task_manager'; +import { PluginSetupContract as TaskManagerPluginSetupContract } from '../../task_manager/plugin'; + +export const getMockTaskInstance = (): TaskInstance => ({ + state: { runs: 0, stats: {} }, + taskType: 'test', + params: {}, +}); const defaultMockSavedObjects = [ { @@ -20,10 +27,15 @@ const defaultMockSavedObjects = [ const defaultMockTaskDocs = [getMockTaskInstance()]; -export const getMockCallWithInternal = (hits: SavedObjectDoc[] = defaultMockSavedObjects) => { - return (): Promise => { +export const getMockEs = (mockCallWithInternal: APICaller = getMockCallWithInternal()) => + (({ + createClient: () => ({ callAsInternalUser: mockCallWithInternal }), + } as unknown) as CoreSetup['elasticsearch']); + +export const getMockCallWithInternal = (hits: unknown[] = defaultMockSavedObjects): APICaller => { + return ((() => { return Promise.resolve({ hits: { hits } }); - }; + }) as unknown) as APICaller; }; export const getMockTaskFetch = (docs: TaskInstance[] = defaultMockTaskDocs) => { @@ -36,24 +48,13 @@ export const getMockConfig = () => { }; }; -export const getMockKbnServer = ( - mockCallWithInternal = getMockCallWithInternal(), - mockTaskFetch = getMockTaskFetch(), - mockConfig = getMockConfig() -): HapiServer => ({ - plugins: { - elasticsearch: { - getCluster: (cluster: string) => ({ - callWithInternalUser: mockCallWithInternal, - }), - }, - xpack_main: {}, - task_manager: { - registerTaskDefinitions: (opts: any) => undefined, - ensureScheduled: (opts: any) => Promise.resolve(), - fetch: mockTaskFetch, - }, - }, - config: () => mockConfig, - log: () => undefined, +export const getMockTaskManager = (fetch: any = getMockTaskFetch()) => + (({ + registerTaskDefinitions: () => undefined, + ensureScheduled: () => Promise.resolve(), + fetch, + } as unknown) as TaskManagerPluginSetupContract); + +export const getCluster = () => ({ + callWithInternalUser: getMockCallWithInternal(), }); diff --git a/x-pack/legacy/plugins/reporting/server/browsers/chromium/driver_factory/index.ts b/x-pack/legacy/plugins/reporting/server/browsers/chromium/driver_factory/index.ts index ca26f7d41c12..daa7df343f8a 100644 --- a/x-pack/legacy/plugins/reporting/server/browsers/chromium/driver_factory/index.ts +++ b/x-pack/legacy/plugins/reporting/server/browsers/chromium/driver_factory/index.ts @@ -15,7 +15,7 @@ import { } from 'puppeteer'; import del from 'del'; import * as Rx from 'rxjs'; -import { ignoreElements, mergeMap, tap } from 'rxjs/operators'; +import { ignoreElements, map, mergeMap, tap } from 'rxjs/operators'; import { InnerSubscriber } from 'rxjs/internal/InnerSubscriber'; import { BrowserConfig, NetworkPolicy } from '../../../../types'; @@ -144,7 +144,7 @@ export class HeadlessChromiumDriverFactory { terminate$ .pipe( tap(signal => { - this.logger.debug(`Observer got signal: ${signal}`); + this.logger.debug(`Termination signal received: ${signal}`); }), ignoreElements() ) @@ -156,7 +156,6 @@ export class HeadlessChromiumDriverFactory { this.getProcessLogger(browser).subscribe(); const driver$ = Rx.of(new HeadlessChromiumDriver(page, { inspect: this.browserConfig.inspect, networkPolicy: this.networkPolicy })); // prettier-ignore - const exit$ = this.getPageExit(browser, page); observer.next({ driver$, exit$ }); @@ -173,9 +172,9 @@ export class HeadlessChromiumDriverFactory { }); } - getBrowserLogger(page: Page): Rx.Observable { - return Rx.fromEvent(page, 'console').pipe( - tap(line => { + getBrowserLogger(page: Page): Rx.Observable { + const consoleMessages$ = Rx.fromEvent(page, 'console').pipe( + map(line => { if (line.type() === 'error') { this.logger.error(line.text(), ['headless-browser-console']); } else { @@ -183,6 +182,19 @@ export class HeadlessChromiumDriverFactory { } }) ); + + const pageRequestFailed$ = Rx.fromEvent(page, 'requestfailed').pipe( + map(req => { + const failure = req.failure && req.failure(); + if (failure) { + this.logger.warning( + `Request to [${req.url()}] failed! [${failure.errorText}]. This error will be ignored.` + ); + } + }) + ); + + return Rx.merge(consoleMessages$, pageRequestFailed$); } getProcessLogger(browser: Browser) { @@ -208,18 +220,6 @@ export class HeadlessChromiumDriverFactory { mergeMap(err => Rx.throwError(err)) ); - const pageRequestFailed$ = Rx.fromEvent(page, 'requestfailed').pipe( - mergeMap(req => { - const failure = req.failure && req.failure(); - if (failure) { - return Rx.throwError( - new Error(`Request to [${req.url()}] failed! [${failure.errorText}]`) - ); - } - return Rx.throwError(new Error(`Unknown failure!`)); - }) - ); - const browserDisconnect$ = Rx.fromEvent(browser, 'disconnected').pipe( mergeMap(() => Rx.throwError( @@ -230,11 +230,6 @@ export class HeadlessChromiumDriverFactory { ) ); - return Rx.merge( - pageError$, - uncaughtExceptionPageError$, - pageRequestFailed$, - browserDisconnect$ - ); + return Rx.merge(pageError$, uncaughtExceptionPageError$, browserDisconnect$); } } diff --git a/x-pack/legacy/plugins/security/index.js b/x-pack/legacy/plugins/security/index.js index 1d798a4a2bc4..115dd8b9b820 100644 --- a/x-pack/legacy/plugins/security/index.js +++ b/x-pack/legacy/plugins/security/index.js @@ -132,7 +132,6 @@ export const security = (kibana) => new kibana.Plugin({ server.plugins.kibana.systemApi ), cspRules: createCSPRuleString(config.get('csp.rules')), - kibanaIndexName: config.get('kibana.index'), }); // Legacy xPack Info endpoint returns whatever we return in a callback for `registerLicenseCheckResultsGenerator` diff --git a/x-pack/legacy/plugins/siem/package.json b/x-pack/legacy/plugins/siem/package.json index d239961ee75d..ef6431327b5a 100644 --- a/x-pack/legacy/plugins/siem/package.json +++ b/x-pack/legacy/plugins/siem/package.json @@ -5,6 +5,7 @@ "private": true, "license": "Elastic-License", "scripts": { + "extract-mitre-attacks": "node scripts/extract_tactics_techniques_mitre.js & node ../../../../scripts/eslint ./public/pages/detection_engine/mitre/mitre_tactics_techniques.ts --fix", "build-graphql-types": "node scripts/generate_types_from_graphql.js", "cypress:open": "../../../node_modules/.bin/cypress open", "cypress:run": "../../../node_modules/.bin/cypress run --spec ./cypress/integration/**/*.spec.ts --reporter ../../../node_modules/cypress-multi-reporters --reporter-options configFile=./reporter_config.json; status=$?; ../../../node_modules/.bin/mochawesome-merge --reportDir ../../../../target/kibana-siem/cypress/results > ../../../../target/kibana-siem/cypress/results/output.json; ../../../../node_modules/.bin/marge ../../../../target/kibana-siem/cypress/results/output.json --reportDir ../../../../target/kibana-siem/cypress/results; mkdir -p ../../../../target/junit && cp ../../../../target/kibana-siem/cypress/results/*.xml ../../../../target/junit/ && exit $status;" diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map.tsx index cb73cf73b8d0..1658002408fb 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map.tsx @@ -9,7 +9,6 @@ import React, { useEffect, useState } from 'react'; import { createPortalNode, InPortal } from 'react-reverse-portal'; import styled, { css } from 'styled-components'; import { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } from 'ui/documentation_links'; -import { SavedObjectFinder } from 'ui/saved_objects/components/saved_object_finder'; import { EmbeddablePanel } from '../../../../../../../src/legacy/core_plugins/embeddable_api/public/np_ready/public'; import { start } from '../../../../../../../src/legacy/core_plugins/embeddable_api/public/np_ready/public/legacy'; @@ -29,6 +28,10 @@ import { MapToolTip } from './map_tool_tip/map_tool_tip'; import * as i18n from './translations'; import { MapEmbeddable, SetQuery } from './types'; import { Query, esFilters } from '../../../../../../../src/plugins/data/public'; +import { + SavedObjectFinderProps, + SavedObjectFinderUi, +} from '../../../../../../../src/plugins/kibana_react/public'; interface EmbeddableMapProps { maintainRatio?: boolean; @@ -176,6 +179,10 @@ export const EmbeddedMapComponent = ({ } }, [startDate, endDate]); + const SavedObjectFinder = (props: SavedObjectFinderProps) => ( + + ); + return isError ? null : ( diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/add_item_form/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/add_item_form/index.tsx index 04bca0cdbd61..e972cd21b6be 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/add_item_form/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/add_item_form/index.tsx @@ -9,7 +9,7 @@ import { isEmpty } from 'lodash/fp'; import React, { ChangeEvent, useCallback, useEffect, useState, useRef } from 'react'; import { FieldHook, getFieldValidityAndErrorMessage } from '../shared_imports'; -import * as I18n from './translations'; +import * as CreateRuleI18n from '../../translations'; interface AddItemProps { addText: string; @@ -34,18 +34,21 @@ export const AddItem = ({ addText, dataTestSubj, field, idAria, isDisabled }: Ad ...inputsRef.current.slice(0, index), ...inputsRef.current.slice(index + 1), ]; - if (inputsRef.current[index] != null) { - inputsRef.current[index].value = 're-render'; - } + inputsRef.current = inputsRef.current.map((ref, i) => { + if (i >= index && inputsRef.current[index] != null) { + ref.value = 're-render'; + } + return ref; + }); }, [field] ); const addItem = useCallback(() => { const values = field.value as string[]; - if (!isEmpty(values[values.length - 1])) { + if (!isEmpty(values) && values[values.length - 1]) { field.setValue([...values, '']); - } else { + } else if (isEmpty(values)) { field.setValue(['']); } }, [field]); @@ -62,9 +65,12 @@ export const AddItem = ({ addText, dataTestSubj, field, idAria, isDisabled }: Ad ...inputsRef.current.slice(index + 1), ]; setHaveBeenKeyboardDeleted(inputsRef.current.length - 1); - if (inputsRef.current[index] != null) { - inputsRef.current[index].value = 're-render'; - } + inputsRef.current = inputsRef.current.map((ref, i) => { + if (i >= index && inputsRef.current[index] != null) { + ref.value = 're-render'; + } + return ref; + }); } else { field.setValue([...values.slice(0, index), value, ...values.slice(index + 1)]); } @@ -114,7 +120,8 @@ export const AddItem = ({ addText, dataTestSubj, field, idAria, isDisabled }: Ad ...(index === values.length - 1 ? { inputRef: handleLastInputRef.bind(null, index) } : {}), - ...(inputsRef.current[index] != null && inputsRef.current[index].value !== item + ...((inputsRef.current[index] != null && inputsRef.current[index].value !== item) || + inputsRef.current[index] == null ? { value: item } : {}), }; @@ -127,7 +134,7 @@ export const AddItem = ({ addText, dataTestSubj, field, idAria, isDisabled }: Ad iconType="trash" isDisabled={isDisabled} onClick={() => removeItem(index)} - aria-label={I18n.DELETE} + aria-label={CreateRuleI18n.DELETE} /> } onChange={e => updateItem(e, index)} diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/description_step/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/description_step/index.tsx index 3e8147e5ca3c..29e1bc228e06 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/description_step/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/description_step/index.tsx @@ -4,7 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiBadge, EuiDescriptionList, EuiFlexGroup, EuiFlexItem, EuiTextArea } from '@elastic/eui'; +import { + EuiBadge, + EuiDescriptionList, + EuiFlexGroup, + EuiFlexItem, + EuiTextArea, + EuiLink, + EuiText, + EuiListGroup, +} from '@elastic/eui'; import { isEmpty, chunk, get, pick } from 'lodash/fp'; import React, { memo, ReactNode } from 'react'; import styled from 'styled-components'; @@ -20,6 +29,9 @@ import { FilterLabel } from './filter_label'; import { FormSchema } from '../shared_imports'; import * as I18n from './translations'; +import { IMitreEnterpriseAttack } from '../../types'; +import { tacticsOptions, techniquesOptions } from '../../../mitre/mitre_tactics_techniques'; + interface StepRuleDescriptionProps { data: unknown; indexPatterns?: IIndexPattern; @@ -36,16 +48,34 @@ const EuiFlexItemWidth = styled(EuiFlexItem)` width: 50%; `; +const MyEuiListGroup = styled(EuiListGroup)` + padding: 0px; + .euiListGroupItem__button { + padding: 0px; + } +`; + +const ThreatsEuiFlexGroup = styled(EuiFlexGroup)` + .euiFlexItem { + margin-bottom: 0px; + } +`; + export const StepRuleDescription = memo( ({ data, indexPatterns, schema }) => { const keys = Object.keys(schema); + const listItems = keys.reduce( + (acc: ListItems[], key: string) => [ + ...acc, + ...buildListItems(data, pick(key, schema), indexPatterns), + ], + [] + ); return ( - {chunk(keys.includes('queryBar') ? 3 : Math.ceil(keys.length / 2), keys).map(key => ( - - + {chunk(Math.ceil(listItems.length / 2), listItems).map((chunckListItems, index) => ( + + ))} @@ -77,7 +107,9 @@ const getDescriptionItem = ( value: unknown, indexPatterns?: IIndexPattern ): ListItems[] => { - if (field === 'queryBar' && indexPatterns != null) { + if (field === 'useIndicesConfig') { + return []; + } else if (field === 'queryBar' && indexPatterns != null) { const filters = get('queryBar.filters', value) as esFilters.Filter[]; const query = get('queryBar.query', value) as Query; const savedId = get('queryBar.saved_id', value); @@ -123,6 +155,50 @@ const getDescriptionItem = ( ]; } return items; + } else if (field === 'threats') { + const threats: IMitreEnterpriseAttack[] = get(field, value).filter( + (threat: IMitreEnterpriseAttack) => threat.tactic.name !== 'none' + ); + if (threats.length > 0) { + return [ + { + title: label, + description: ( + + {threats.map((threat, index) => { + const tactic = tacticsOptions.find(t => t.name === threat.tactic.name); + return ( + + +
+ + {tactic != null ? tactic.text : ''} + +
+ { + const myTechnique = techniquesOptions.find( + t => t.name === technique.name + ); + return { + label: myTechnique != null ? myTechnique.label : '', + href: technique.reference, + target: '_blank', + }; + })} + /> +
+
+ ); + })} +
+ ), + }, + ]; + } + return []; } else if (field === 'description') { return [ { @@ -131,20 +207,26 @@ const getDescriptionItem = ( }, ]; } else if (Array.isArray(get(field, value))) { - return [ - { - title: label, - description: ( - - {get(field, value).map((val: string) => ( - - {val} - - ))} - - ), - }, - ]; + const values: string[] = get(field, value); + if (!isEmpty(values) && values.filter(val => !isEmpty(val)).length > 0) { + return [ + { + title: label, + description: ( + + {values.map((val: string) => + isEmpty(val) ? null : ( + + {val} + + ) + )} + + ), + }, + ]; + } + return []; } return [ { diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/mitre/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/mitre/index.tsx new file mode 100644 index 000000000000..6ab4ca4b5144 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/mitre/index.tsx @@ -0,0 +1,171 @@ +/* + * 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, + EuiButtonIcon, + EuiFormRow, + EuiSelect, + EuiSpacer, + EuiFlexGroup, + EuiFlexItem, + EuiComboBox, + EuiFormControlLayout, +} from '@elastic/eui'; +import { isEmpty, kebabCase, camelCase } from 'lodash/fp'; +import React, { ChangeEvent, useCallback } from 'react'; +import styled from 'styled-components'; + +import { tacticsOptions, techniquesOptions } from '../../../mitre/mitre_tactics_techniques'; +import * as CreateRuleI18n from '../../translations'; +import { FieldHook, getFieldValidityAndErrorMessage } from '../shared_imports'; +import * as I18n from './translations'; +import { IMitreEnterpriseAttack } from '../../types'; + +const MyEuiFormControlLayout = styled(EuiFormControlLayout)` + &.euiFormControlLayout--compressed { + height: fit-content !important; + } +`; +interface AddItemProps { + field: FieldHook; + dataTestSubj: string; + idAria: string; + isDisabled: boolean; +} + +export const AddMitreThreat = ({ dataTestSubj, field, idAria, isDisabled }: AddItemProps) => { + const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field); + + const removeItem = useCallback( + (index: number) => { + const values = field.value as string[]; + field.setValue([...values.slice(0, index), ...values.slice(index + 1)]); + }, + [field] + ); + + const addItem = useCallback(() => { + const values = field.value as IMitreEnterpriseAttack[]; + if (!isEmpty(values[values.length - 1])) { + field.setValue([ + ...values, + { tactic: { id: 'none', name: 'none', reference: 'none' }, techniques: [] }, + ]); + } else { + field.setValue([{ tactic: { id: 'none', name: 'none', reference: 'none' }, techniques: [] }]); + } + }, [field]); + + const updateTactic = useCallback( + (index: number, event: ChangeEvent) => { + const values = field.value as IMitreEnterpriseAttack[]; + const { id, reference, name } = tacticsOptions.find(t => t.value === event.target.value) || { + id: '', + name: '', + reference: '', + }; + field.setValue([ + ...values.slice(0, index), + { + ...values[index], + tactic: { id, reference, name }, + techniques: [], + }, + ...values.slice(index + 1), + ]); + }, + [field] + ); + + const updateTechniques = useCallback( + (index: number, selectedOptions: unknown[]) => { + field.setValue([ + ...values.slice(0, index), + { + ...values[index], + techniques: selectedOptions, + }, + ...values.slice(index + 1), + ]); + }, + [field] + ); + + const values = field.value as IMitreEnterpriseAttack[]; + + return ( + + <> + {values.map((item, index) => { + const euiSelectFieldProps = { + disabled: isDisabled, + }; + return ( +
+ + + ({ text: t.text, value: t.value })), + ]} + aria-label="" + onChange={updateTactic.bind(null, index)} + prepend={I18n.TACTIC} + compressed + fullWidth={false} + value={camelCase(item.tactic.name)} + {...euiSelectFieldProps} + /> + + + + + t.tactics.includes(kebabCase(item.tactic.name)) + )} + selectedOptions={item.techniques} + onChange={updateTechniques.bind(null, index)} + isDisabled={isDisabled} + fullWidth={true} + /> + + + + removeItem(index)} + aria-label={CreateRuleI18n.DELETE} + /> + + + {values.length - 1 !== index && } +
+ ); + })} + + {I18n.ADD_MITRE_ATTACK} + + +
+ ); +}; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/mitre/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/mitre/translations.ts new file mode 100644 index 000000000000..22ee6cc3ef91 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/mitre/translations.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 { i18n } from '@kbn/i18n'; + +export const TACTIC = i18n.translate('xpack.siem.detectionEngine.mitreAttack.tacticsDescription', { + defaultMessage: 'Tactic', +}); + +export const TECHNIQUES = i18n.translate( + 'xpack.siem.detectionEngine.mitreAttack.techniquesDescription', + { + defaultMessage: 'Techniques', + } +); + +export const ADD_MITRE_ATTACK = i18n.translate('xpack.siem.detectionEngine.mitreAttack.addTitle', { + defaultMessage: 'Add MITRE ATT&CK threat', +}); + +export const TECHNIQUES_PLACEHOLDER = i18n.translate( + 'xpack.siem.detectionEngine.mitreAttack.techniquesPlaceHolderDescription', + { + defaultMessage: 'Select techniques ...', + } +); + +export const TACTIC_PLACEHOLDER = i18n.translate( + 'xpack.siem.detectionEngine.mitreAttack.tacticPlaceHolderDescription', + { + defaultMessage: 'Select tactic ...', + } +); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/query_bar/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/query_bar/index.tsx index 92b2f557d4ce..8dc402f00e62 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/query_bar/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/query_bar/index.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiFormRow } from '@elastic/eui'; +import { EuiFormRow, EuiMutationObserver } from '@elastic/eui'; import { isEqual } from 'lodash/fp'; import React, { useCallback, useEffect, useState } from 'react'; import { Subscription } from 'rxjs'; @@ -36,6 +36,7 @@ interface QueryBarDefineRuleProps { idAria: string; isLoading: boolean; indexPattern: IIndexPattern; + resizeParentContainer?: (height: number) => void; } const StyledEuiFormRow = styled(EuiFormRow)` @@ -60,7 +61,9 @@ export const QueryBarDefineRule = ({ idAria, indexPattern, isLoading = false, + resizeParentContainer, }: QueryBarDefineRuleProps) => { + const [originalHeight, setOriginalHeight] = useState(-1); const [savedQuery, setSavedQuery] = useState(null); const [queryDraft, setQueryDraft] = useState({ query: '', language: 'kuery' }); const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field); @@ -165,6 +168,27 @@ export const QueryBarDefineRule = ({ [field.value] ); + const onMutation = (event: unknown, observer: unknown) => { + if (resizeParentContainer != null) { + const suggestionContainer = document.getElementById('kbnTypeahead__items'); + if (suggestionContainer != null) { + const box = suggestionContainer.getBoundingClientRect(); + const accordionContainer = document.getElementById('define-rule'); + if (accordionContainer != null) { + const accordionBox = accordionContainer.getBoundingClientRect(); + if (originalHeight === -1 || accordionBox.height < originalHeight + box.height) { + resizeParentContainer(originalHeight + box.height - 100); + } + if (originalHeight === -1) { + setOriginalHeight(accordionBox.height); + } + } + } else { + resizeParentContainer(-1); + } + } + }; + return ( - + + {mutationRef => ( +
+ +
+ )} +
); }; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_about_rule/default_value.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_about_rule/default_value.ts index 7c4d78f36447..504b5ca85a3a 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_about_rule/default_value.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_about_rule/default_value.ts @@ -15,4 +15,11 @@ export const defaultValue: AboutStepRule = { references: [''], falsePositives: [''], tags: [], + threats: [ + { + framework: 'MITRE ATT&CK', + tactic: { id: 'none', name: 'none', reference: 'none' }, + techniques: [], + }, + ], }; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_about_rule/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_about_rule/index.tsx index 56830f252748..aeb70061c44b 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_about_rule/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_about_rule/index.tsx @@ -16,6 +16,7 @@ import { defaultValue } from './default_value'; import { schema } from './schema'; import * as I18n from './translations'; import { StepRuleDescription } from '../description_step'; +import { AddMitreThreat } from '../mitre'; const CommonUseField = getUseField({ component: Field }); @@ -114,6 +115,16 @@ export const StepAboutRule = memo(({ isEditView, isLoading, setSt dataTestSubj: 'detectionEngineStepAboutRuleFalsePositives', }} /> + {CreateRuleI18n.OPTIONAL_FIELD}, }, + threats: { + label: i18n.translate( + 'xpack.siem.detectionEngine.createRule.stepAboutRule.fieldMitreThreatLabel', + { + defaultMessage: 'MITRE ATT&CK', + } + ), + labelAppend: {CreateRuleI18n.OPTIONAL_FIELD}, + validations: [ + { + validator: ( + ...args: Parameters + ): ReturnType> | undefined => { + const [{ value, path }] = args; + let hasError = false; + (value as IMitreEnterpriseAttack[]).forEach(v => { + if (isEmpty(v.tactic.name) || (v.tactic.name !== 'none' && isEmpty(v.techniques))) { + hasError = true; + } + }); + return hasError + ? { + code: 'ERR_FIELD_MISSING', + path, + message: I18n.CUSTOM_MITRE_ATTACK_TECHNIQUES_REQUIRED, + } + : undefined; + }, + }, + ], + }, tags: { type: FIELD_TYPES.COMBO_BOX, label: i18n.translate('xpack.siem.detectionEngine.createRule.stepAboutRule.fieldTagsLabel', { diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_about_rule/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_about_rule/translations.ts index bd759b345d70..017d4fe6fdf4 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_about_rule/translations.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_about_rule/translations.ts @@ -47,3 +47,10 @@ export const CRITICAL = i18n.translate( defaultMessage: 'Critical', } ); + +export const CUSTOM_MITRE_ATTACK_TECHNIQUES_REQUIRED = i18n.translate( + 'xpack.siem.detectionEngine.createRule.stepDefineRule.customMitreAttackTechniquesFieldRequiredError', + { + defaultMessage: 'At least one Technique is required with a Tactic.', + } +); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_define_rule/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_define_rule/index.tsx index 26306d357392..6954bd6bf733 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_define_rule/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_define_rule/index.tsx @@ -6,11 +6,11 @@ import { EuiHorizontalRule, EuiFlexGroup, EuiFlexItem, EuiButton } from '@elastic/eui'; import { isEqual } from 'lodash/fp'; -import React, { memo, useCallback, useEffect, useState } from 'react'; +import React, { memo, useCallback, useState } from 'react'; import { IIndexPattern } from '../../../../../../../../../../src/plugins/data/public'; import { useFetchIndexPatterns } from '../../../../../containers/detection_engine/rules/fetch_index_patterns'; -import { DEFAULT_INDEX_KEY, DEFAULT_SIGNALS_INDEX_KEY } from '../../../../../../common/constants'; +import { DEFAULT_INDEX_KEY } from '../../../../../../common/constants'; import { useKibanaUiSetting } from '../../../../../lib/settings/use_kibana_ui_setting'; import * as CreateRuleI18n from '../../translations'; import { DefineStepRule, RuleStep, RuleStepProps } from '../../types'; @@ -22,148 +22,133 @@ import * as I18n from './translations'; const CommonUseField = getUseField({ component: Field }); -export const StepDefineRule = memo(({ isEditView, isLoading, setStepData }) => { - const [initializeOutputIndex, setInitializeOutputIndex] = useState(true); - const [localUseIndicesConfig, setLocalUseIndicesConfig] = useState(''); - const [ - { indexPatterns: indexPatternQueryBar, isLoading: indexPatternLoadingQueryBar }, - setIndices, - ] = useFetchIndexPatterns(); - const [indicesConfig] = useKibanaUiSetting(DEFAULT_INDEX_KEY); - const [signalIndexConfig] = useKibanaUiSetting(DEFAULT_SIGNALS_INDEX_KEY); - const [myStepData, setMyStepData] = useState({ - index: indicesConfig || [], - isNew: true, - outputIndex: signalIndexConfig, - queryBar: { - query: { query: '', language: 'kuery' }, - filters: [], - saved_id: null, - }, - useIndicesConfig: 'true', - }); - const { form } = useForm({ - schema, - defaultValue: myStepData, - options: { stripEmptyFields: false }, - }); +export const StepDefineRule = memo( + ({ isEditView, isLoading, resizeParentContainer, setStepData }) => { + const [localUseIndicesConfig, setLocalUseIndicesConfig] = useState(''); + const [ + { indexPatterns: indexPatternQueryBar, isLoading: indexPatternLoadingQueryBar }, + setIndices, + ] = useFetchIndexPatterns(); + const [indicesConfig] = useKibanaUiSetting(DEFAULT_INDEX_KEY); + const [myStepData, setMyStepData] = useState({ + index: indicesConfig || [], + isNew: true, + queryBar: { + query: { query: '', language: 'kuery' }, + filters: [], + saved_id: null, + }, + useIndicesConfig: 'true', + }); + const { form } = useForm({ + schema, + defaultValue: myStepData, + options: { stripEmptyFields: false }, + }); - const onSubmit = useCallback(async () => { - const { isValid, data } = await form.submit(); - if (isValid) { - setStepData(RuleStep.defineRule, data, isValid); - setMyStepData({ ...data, isNew: false } as DefineStepRule); - } - }, [form]); + const onSubmit = useCallback(async () => { + const { isValid, data } = await form.submit(); + if (isValid) { + setStepData(RuleStep.defineRule, data, isValid); + setMyStepData({ ...data, isNew: false } as DefineStepRule); + } + }, [form]); - useEffect(() => { - if (signalIndexConfig != null && initializeOutputIndex) { - const outputIndexField = form.getFields().outputIndex; - outputIndexField.setValue(signalIndexConfig); - setInitializeOutputIndex(false); - } - }, [initializeOutputIndex, signalIndexConfig, form]); - - return isEditView && myStepData != null ? ( - - ) : ( - <> -
- - + ) : ( + <> + + + + - - - - {({ useIndicesConfig }) => { - if (localUseIndicesConfig !== useIndicesConfig) { - const indexField = form.getFields().index; - if ( - indexField != null && - useIndicesConfig === 'true' && - !isEqual(indexField.value, indicesConfig) - ) { - indexField.setValue(indicesConfig); - setIndices(indicesConfig); - } else if ( - indexField != null && - useIndicesConfig === 'false' && - isEqual(indexField.value, indicesConfig) - ) { - indexField.setValue([]); - setIndices([]); + isLoading: indexPatternLoadingQueryBar, + dataTestSubj: 'detectionEngineStepDefineRuleQueryBar', + resizeParentContainer, + }} + /> + + {({ useIndicesConfig }) => { + if (localUseIndicesConfig !== useIndicesConfig) { + const indexField = form.getFields().index; + if ( + indexField != null && + useIndicesConfig === 'true' && + !isEqual(indexField.value, indicesConfig) + ) { + indexField.setValue(indicesConfig); + setIndices(indicesConfig); + } else if ( + indexField != null && + useIndicesConfig === 'false' && + isEqual(indexField.value, indicesConfig) + ) { + indexField.setValue([]); + setIndices([]); + } + setLocalUseIndicesConfig(useIndicesConfig); } - setLocalUseIndicesConfig(useIndicesConfig); - } - return null; - }} - - - - - - - {myStepData.isNew ? CreateRuleI18n.CONTINUE : CreateRuleI18n.UPDATE} - - - - - ); -}); + return null; + }} + + + + + + + {myStepData.isNew ? CreateRuleI18n.CONTINUE : CreateRuleI18n.UPDATE} + + + + + ); + } +); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_define_rule/schema.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_define_rule/schema.tsx index 9f1644e73bf0..0f6c5f72e168 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_define_rule/schema.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_define_rule/schema.tsx @@ -26,27 +26,6 @@ import { CUSTOM_QUERY_REQUIRED, INVALID_CUSTOM_QUERY } from './translations'; const { emptyField } = fieldValidators; export const schema: FormSchema = { - outputIndex: { - type: FIELD_TYPES.TEXT, - label: i18n.translate( - 'xpack.siem.detectionEngine.createRule.stepDefineRule.fieldOutputIndiceNameLabel', - { - defaultMessage: 'Output index name', - } - ), - validations: [ - { - validator: emptyField( - i18n.translate( - 'xpack.siem.detectionEngine.createRule.stepDefineRule.outputIndiceNameFieldRequiredError', - { - defaultMessage: 'An output indice name for signals is required.', - } - ) - ), - }, - ], - }, useIndicesConfig: { type: FIELD_TYPES.RADIO_GROUP, label: i18n.translate( diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/helpers.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/helpers.ts index b864260dd333..f6546a680ad8 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/helpers.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/helpers.ts @@ -40,13 +40,12 @@ const getTimeTypeValue = (time: string): { unit: string; value: number } => { }; const formatDefineStepData = (defineStepData: DefineStepRule): DefineStepRuleJson => { - const { queryBar, useIndicesConfig, outputIndex, ...rest } = defineStepData; + const { queryBar, useIndicesConfig, ...rest } = defineStepData; const { filters, query, saved_id: savedId } = queryBar; return { ...rest, language: query.language, filters, - output_index: outputIndex, query: query.query as string, ...(savedId != null ? { saved_id: savedId } : {}), }; @@ -69,12 +68,22 @@ const formatScheduleStepData = (scheduleData: ScheduleStepRule): ScheduleStepRul }; const formatAboutStepData = (aboutStepData: AboutStepRule): AboutStepRuleJson => { - const { falsePositives, references, riskScore, ...rest } = aboutStepData; + const { falsePositives, references, riskScore, threats, ...rest } = aboutStepData; return { false_positives: falsePositives.filter(item => !isEmpty(item)), references: references.filter(item => !isEmpty(item)), risk_score: riskScore, + threats: threats + .filter(threat => threat.tactic.name !== 'none') + .map(threat => ({ + ...threat, + framework: 'MITRE ATT&CK', + techniques: threat.techniques.map(technique => { + const { id, name, reference } = technique; + return { id, name, reference }; + }), + })), ...rest, }; }; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/index.tsx index 878c7171d19e..393b72d16b0a 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/index.tsx @@ -7,6 +7,7 @@ import { EuiButtonEmpty, EuiAccordion, EuiHorizontalRule, EuiPanel, EuiSpacer } from '@elastic/eui'; import React, { useCallback, useRef, useState } from 'react'; import { Redirect } from 'react-router-dom'; +import styled from 'styled-components'; import { HeaderPage } from '../../../components/header_page'; import { WrapperPage } from '../../../components/wrapper_page'; @@ -24,7 +25,16 @@ import { DETECTION_ENGINE_PAGE_NAME } from '../../../components/link_to/redirect const stepsRuleOrder = [RuleStep.defineRule, RuleStep.aboutRule, RuleStep.scheduleRule]; +const ResizeEuiPanel = styled(EuiPanel)<{ + height?: number; +}>` + .euiAccordion__childWrapper { + height: ${props => (props.height !== -1 ? `${props.height}px !important` : 'auto')}; + } +`; + export const CreateRuleComponent = React.memo(() => { + const [heightAccordion, setHeightAccordion] = useState(-1); const [openAccordionId, setOpenAccordionId] = useState(RuleStep.defineRule); const defineRuleRef = useRef(null); const aboutRuleRef = useRef(null); @@ -169,7 +179,7 @@ export const CreateRuleComponent = React.memo(() => { isLoading={isLoading} title={i18n.PAGE_TITLE} /> - + { isEditView={isStepRuleInEditView[RuleStep.defineRule]} isLoading={isLoading} setStepData={setStepData} + resizeParentContainer={height => setHeightAccordion(height)} /> - + void; isEditView: boolean; isLoading: boolean; + resizeParentContainer?: (height: number) => void; } interface StepRuleData { @@ -36,10 +37,10 @@ export interface AboutStepRule extends StepRuleData { references: string[]; falsePositives: string[]; tags: string[]; + threats: IMitreEnterpriseAttack[]; } export interface DefineStepRule extends StepRuleData { - outputIndex: string; useIndicesConfig: string; index: string[]; queryBar: FieldValueQueryBar; @@ -53,7 +54,6 @@ export interface ScheduleStepRule extends StepRuleData { } export interface DefineStepRuleJson { - output_index: string; index: string[]; filters: esFilters.Filter[]; saved_id?: string; @@ -69,8 +69,20 @@ export interface AboutStepRuleJson { references: string[]; false_positives: string[]; tags: string[]; + threats: IMitreEnterpriseAttack[]; } export type ScheduleStepRuleJson = ScheduleStepRule; export type FormatRuleType = 'query' | 'saved_query'; + +export interface IMitreAttack { + id: string; + name: string; + reference: string; +} +export interface IMitreEnterpriseAttack { + framework: string; + tactic: IMitreAttack; + techniques: IMitreAttack[]; +} diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/mitre/mitre_tactics_techniques.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/mitre/mitre_tactics_techniques.ts new file mode 100644 index 000000000000..160e006c4d26 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/mitre/mitre_tactics_techniques.ts @@ -0,0 +1,4696 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +import { MitreTacticsOptions, MitreTechniquesOptions } from './types'; + +export const tactics = [ + { + name: 'Collection', + id: 'TA0009', + reference: 'https://attack.mitre.org/tactics/TA0009', + }, + { + name: 'Command and Control', + id: 'TA0011', + reference: 'https://attack.mitre.org/tactics/TA0011', + }, + { + name: 'Credential Access', + id: 'TA0006', + reference: 'https://attack.mitre.org/tactics/TA0006', + }, + { + name: 'Defense Evasion', + id: 'TA0005', + reference: 'https://attack.mitre.org/tactics/TA0005', + }, + { + name: 'Discovery', + id: 'TA0007', + reference: 'https://attack.mitre.org/tactics/TA0007', + }, + { + name: 'Execution', + id: 'TA0002', + reference: 'https://attack.mitre.org/tactics/TA0002', + }, + { + name: 'Exfiltration', + id: 'TA0010', + reference: 'https://attack.mitre.org/tactics/TA0010', + }, + { + name: 'Impact', + id: 'TA0040', + reference: 'https://attack.mitre.org/tactics/TA0040', + }, + { + name: 'Initial Access', + id: 'TA0001', + reference: 'https://attack.mitre.org/tactics/TA0001', + }, + { + name: 'Lateral Movement', + id: 'TA0008', + reference: 'https://attack.mitre.org/tactics/TA0008', + }, + { + name: 'Persistence', + id: 'TA0003', + reference: 'https://attack.mitre.org/tactics/TA0003', + }, + { + name: 'Privilege Escalation', + id: 'TA0004', + reference: 'https://attack.mitre.org/tactics/TA0004', + }, +]; + +export const tacticsOptions: MitreTacticsOptions[] = [ + { + id: 'TA0009', + name: 'Collection', + reference: 'https://attack.mitre.org/tactics/TA0009', + text: i18n.translate('xpack.siem.detectionEngine.mitreAttackTactics.collectionDescription', { + defaultMessage: 'Collection (TA0009)', + }), + value: 'collection', + }, + { + id: 'TA0011', + name: 'Command and Control', + reference: 'https://attack.mitre.org/tactics/TA0011', + text: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTactics.commandAndControlDescription', + { defaultMessage: 'Command and Control (TA0011)' } + ), + value: 'commandAndControl', + }, + { + id: 'TA0006', + name: 'Credential Access', + reference: 'https://attack.mitre.org/tactics/TA0006', + text: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTactics.credentialAccessDescription', + { defaultMessage: 'Credential Access (TA0006)' } + ), + value: 'credentialAccess', + }, + { + id: 'TA0005', + name: 'Defense Evasion', + reference: 'https://attack.mitre.org/tactics/TA0005', + text: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTactics.defenseEvasionDescription', + { defaultMessage: 'Defense Evasion (TA0005)' } + ), + value: 'defenseEvasion', + }, + { + id: 'TA0007', + name: 'Discovery', + reference: 'https://attack.mitre.org/tactics/TA0007', + text: i18n.translate('xpack.siem.detectionEngine.mitreAttackTactics.discoveryDescription', { + defaultMessage: 'Discovery (TA0007)', + }), + value: 'discovery', + }, + { + id: 'TA0002', + name: 'Execution', + reference: 'https://attack.mitre.org/tactics/TA0002', + text: i18n.translate('xpack.siem.detectionEngine.mitreAttackTactics.executionDescription', { + defaultMessage: 'Execution (TA0002)', + }), + value: 'execution', + }, + { + id: 'TA0010', + name: 'Exfiltration', + reference: 'https://attack.mitre.org/tactics/TA0010', + text: i18n.translate('xpack.siem.detectionEngine.mitreAttackTactics.exfiltrationDescription', { + defaultMessage: 'Exfiltration (TA0010)', + }), + value: 'exfiltration', + }, + { + id: 'TA0040', + name: 'Impact', + reference: 'https://attack.mitre.org/tactics/TA0040', + text: i18n.translate('xpack.siem.detectionEngine.mitreAttackTactics.impactDescription', { + defaultMessage: 'Impact (TA0040)', + }), + value: 'impact', + }, + { + id: 'TA0001', + name: 'Initial Access', + reference: 'https://attack.mitre.org/tactics/TA0001', + text: i18n.translate('xpack.siem.detectionEngine.mitreAttackTactics.initialAccessDescription', { + defaultMessage: 'Initial Access (TA0001)', + }), + value: 'initialAccess', + }, + { + id: 'TA0008', + name: 'Lateral Movement', + reference: 'https://attack.mitre.org/tactics/TA0008', + text: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTactics.lateralMovementDescription', + { defaultMessage: 'Lateral Movement (TA0008)' } + ), + value: 'lateralMovement', + }, + { + id: 'TA0003', + name: 'Persistence', + reference: 'https://attack.mitre.org/tactics/TA0003', + text: i18n.translate('xpack.siem.detectionEngine.mitreAttackTactics.persistenceDescription', { + defaultMessage: 'Persistence (TA0003)', + }), + value: 'persistence', + }, + { + id: 'TA0004', + name: 'Privilege Escalation', + reference: 'https://attack.mitre.org/tactics/TA0004', + text: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTactics.privilegeEscalationDescription', + { defaultMessage: 'Privilege Escalation (TA0004)' } + ), + value: 'privilegeEscalation', + }, +]; + +export const techniques = [ + { + name: '.bash_profile and .bashrc', + id: 'T1156', + reference: 'https://attack.mitre.org/techniques/T1156', + tactics: ['persistence'], + }, + { + name: 'Access Token Manipulation', + id: 'T1134', + reference: 'https://attack.mitre.org/techniques/T1134', + tactics: ['defense-evasion', 'privilege-escalation'], + }, + { + name: 'Accessibility Features', + id: 'T1015', + reference: 'https://attack.mitre.org/techniques/T1015', + tactics: ['persistence', 'privilege-escalation'], + }, + { + name: 'Account Access Removal', + id: 'T1531', + reference: 'https://attack.mitre.org/techniques/T1531', + tactics: ['impact'], + }, + { + name: 'Account Discovery', + id: 'T1087', + reference: 'https://attack.mitre.org/techniques/T1087', + tactics: ['discovery'], + }, + { + name: 'Account Manipulation', + id: 'T1098', + reference: 'https://attack.mitre.org/techniques/T1098', + tactics: ['credential-access', 'persistence'], + }, + { + name: 'AppCert DLLs', + id: 'T1182', + reference: 'https://attack.mitre.org/techniques/T1182', + tactics: ['persistence', 'privilege-escalation'], + }, + { + name: 'AppInit DLLs', + id: 'T1103', + reference: 'https://attack.mitre.org/techniques/T1103', + tactics: ['persistence', 'privilege-escalation'], + }, + { + name: 'AppleScript', + id: 'T1155', + reference: 'https://attack.mitre.org/techniques/T1155', + tactics: ['execution', 'lateral-movement'], + }, + { + name: 'Application Access Token', + id: 'T1527', + reference: 'https://attack.mitre.org/techniques/T1527', + tactics: ['defense-evasion', 'lateral-movement'], + }, + { + name: 'Application Deployment Software', + id: 'T1017', + reference: 'https://attack.mitre.org/techniques/T1017', + tactics: ['lateral-movement'], + }, + { + name: 'Application Shimming', + id: 'T1138', + reference: 'https://attack.mitre.org/techniques/T1138', + tactics: ['persistence', 'privilege-escalation'], + }, + { + name: 'Application Window Discovery', + id: 'T1010', + reference: 'https://attack.mitre.org/techniques/T1010', + tactics: ['discovery'], + }, + { + name: 'Audio Capture', + id: 'T1123', + reference: 'https://attack.mitre.org/techniques/T1123', + tactics: ['collection'], + }, + { + name: 'Authentication Package', + id: 'T1131', + reference: 'https://attack.mitre.org/techniques/T1131', + tactics: ['persistence'], + }, + { + name: 'Automated Collection', + id: 'T1119', + reference: 'https://attack.mitre.org/techniques/T1119', + tactics: ['collection'], + }, + { + name: 'Automated Exfiltration', + id: 'T1020', + reference: 'https://attack.mitre.org/techniques/T1020', + tactics: ['exfiltration'], + }, + { + name: 'BITS Jobs', + id: 'T1197', + reference: 'https://attack.mitre.org/techniques/T1197', + tactics: ['defense-evasion', 'persistence'], + }, + { + name: 'Bash History', + id: 'T1139', + reference: 'https://attack.mitre.org/techniques/T1139', + tactics: ['credential-access'], + }, + { + name: 'Binary Padding', + id: 'T1009', + reference: 'https://attack.mitre.org/techniques/T1009', + tactics: ['defense-evasion'], + }, + { + name: 'Bootkit', + id: 'T1067', + reference: 'https://attack.mitre.org/techniques/T1067', + tactics: ['persistence'], + }, + { + name: 'Browser Bookmark Discovery', + id: 'T1217', + reference: 'https://attack.mitre.org/techniques/T1217', + tactics: ['discovery'], + }, + { + name: 'Browser Extensions', + id: 'T1176', + reference: 'https://attack.mitre.org/techniques/T1176', + tactics: ['persistence'], + }, + { + name: 'Brute Force', + id: 'T1110', + reference: 'https://attack.mitre.org/techniques/T1110', + tactics: ['credential-access'], + }, + { + name: 'Bypass User Account Control', + id: 'T1088', + reference: 'https://attack.mitre.org/techniques/T1088', + tactics: ['defense-evasion', 'privilege-escalation'], + }, + { + name: 'CMSTP', + id: 'T1191', + reference: 'https://attack.mitre.org/techniques/T1191', + tactics: ['defense-evasion', 'execution'], + }, + { + name: 'Change Default File Association', + id: 'T1042', + reference: 'https://attack.mitre.org/techniques/T1042', + tactics: ['persistence'], + }, + { + name: 'Clear Command History', + id: 'T1146', + reference: 'https://attack.mitre.org/techniques/T1146', + tactics: ['defense-evasion'], + }, + { + name: 'Clipboard Data', + id: 'T1115', + reference: 'https://attack.mitre.org/techniques/T1115', + tactics: ['collection'], + }, + { + name: 'Cloud Instance Metadata API', + id: 'T1522', + reference: 'https://attack.mitre.org/techniques/T1522', + tactics: ['credential-access'], + }, + { + name: 'Cloud Service Dashboard', + id: 'T1538', + reference: 'https://attack.mitre.org/techniques/T1538', + tactics: ['discovery'], + }, + { + name: 'Cloud Service Discovery', + id: 'T1526', + reference: 'https://attack.mitre.org/techniques/T1526', + tactics: ['discovery'], + }, + { + name: 'Code Signing', + id: 'T1116', + reference: 'https://attack.mitre.org/techniques/T1116', + tactics: ['defense-evasion'], + }, + { + name: 'Command-Line Interface', + id: 'T1059', + reference: 'https://attack.mitre.org/techniques/T1059', + tactics: ['execution'], + }, + { + name: 'Commonly Used Port', + id: 'T1043', + reference: 'https://attack.mitre.org/techniques/T1043', + tactics: ['command-and-control'], + }, + { + name: 'Communication Through Removable Media', + id: 'T1092', + reference: 'https://attack.mitre.org/techniques/T1092', + tactics: ['command-and-control'], + }, + { + name: 'Compile After Delivery', + id: 'T1500', + reference: 'https://attack.mitre.org/techniques/T1500', + tactics: ['defense-evasion'], + }, + { + name: 'Compiled HTML File', + id: 'T1223', + reference: 'https://attack.mitre.org/techniques/T1223', + tactics: ['defense-evasion', 'execution'], + }, + { + name: 'Component Firmware', + id: 'T1109', + reference: 'https://attack.mitre.org/techniques/T1109', + tactics: ['defense-evasion', 'persistence'], + }, + { + name: 'Component Object Model Hijacking', + id: 'T1122', + reference: 'https://attack.mitre.org/techniques/T1122', + tactics: ['defense-evasion', 'persistence'], + }, + { + name: 'Component Object Model and Distributed COM', + id: 'T1175', + reference: 'https://attack.mitre.org/techniques/T1175', + tactics: ['lateral-movement', 'execution'], + }, + { + name: 'Connection Proxy', + id: 'T1090', + reference: 'https://attack.mitre.org/techniques/T1090', + tactics: ['command-and-control', 'defense-evasion'], + }, + { + name: 'Control Panel Items', + id: 'T1196', + reference: 'https://attack.mitre.org/techniques/T1196', + tactics: ['defense-evasion', 'execution'], + }, + { + name: 'Create Account', + id: 'T1136', + reference: 'https://attack.mitre.org/techniques/T1136', + tactics: ['persistence'], + }, + { + name: 'Credential Dumping', + id: 'T1003', + reference: 'https://attack.mitre.org/techniques/T1003', + tactics: ['credential-access'], + }, + { + name: 'Credentials from Web Browsers', + id: 'T1503', + reference: 'https://attack.mitre.org/techniques/T1503', + tactics: ['credential-access'], + }, + { + name: 'Credentials in Files', + id: 'T1081', + reference: 'https://attack.mitre.org/techniques/T1081', + tactics: ['credential-access'], + }, + { + name: 'Credentials in Registry', + id: 'T1214', + reference: 'https://attack.mitre.org/techniques/T1214', + tactics: ['credential-access'], + }, + { + name: 'Custom Command and Control Protocol', + id: 'T1094', + reference: 'https://attack.mitre.org/techniques/T1094', + tactics: ['command-and-control'], + }, + { + name: 'Custom Cryptographic Protocol', + id: 'T1024', + reference: 'https://attack.mitre.org/techniques/T1024', + tactics: ['command-and-control'], + }, + { + name: 'DCShadow', + id: 'T1207', + reference: 'https://attack.mitre.org/techniques/T1207', + tactics: ['defense-evasion'], + }, + { + name: 'DLL Search Order Hijacking', + id: 'T1038', + reference: 'https://attack.mitre.org/techniques/T1038', + tactics: ['persistence', 'privilege-escalation', 'defense-evasion'], + }, + { + name: 'DLL Side-Loading', + id: 'T1073', + reference: 'https://attack.mitre.org/techniques/T1073', + tactics: ['defense-evasion'], + }, + { + name: 'Data Compressed', + id: 'T1002', + reference: 'https://attack.mitre.org/techniques/T1002', + tactics: ['exfiltration'], + }, + { + name: 'Data Destruction', + id: 'T1485', + reference: 'https://attack.mitre.org/techniques/T1485', + tactics: ['impact'], + }, + { + name: 'Data Encoding', + id: 'T1132', + reference: 'https://attack.mitre.org/techniques/T1132', + tactics: ['command-and-control'], + }, + { + name: 'Data Encrypted', + id: 'T1022', + reference: 'https://attack.mitre.org/techniques/T1022', + tactics: ['exfiltration'], + }, + { + name: 'Data Encrypted for Impact', + id: 'T1486', + reference: 'https://attack.mitre.org/techniques/T1486', + tactics: ['impact'], + }, + { + name: 'Data Obfuscation', + id: 'T1001', + reference: 'https://attack.mitre.org/techniques/T1001', + tactics: ['command-and-control'], + }, + { + name: 'Data Staged', + id: 'T1074', + reference: 'https://attack.mitre.org/techniques/T1074', + tactics: ['collection'], + }, + { + name: 'Data Transfer Size Limits', + id: 'T1030', + reference: 'https://attack.mitre.org/techniques/T1030', + tactics: ['exfiltration'], + }, + { + name: 'Data from Cloud Storage Object', + id: 'T1530', + reference: 'https://attack.mitre.org/techniques/T1530', + tactics: ['collection'], + }, + { + name: 'Data from Information Repositories', + id: 'T1213', + reference: 'https://attack.mitre.org/techniques/T1213', + tactics: ['collection'], + }, + { + name: 'Data from Local System', + id: 'T1005', + reference: 'https://attack.mitre.org/techniques/T1005', + tactics: ['collection'], + }, + { + name: 'Data from Network Shared Drive', + id: 'T1039', + reference: 'https://attack.mitre.org/techniques/T1039', + tactics: ['collection'], + }, + { + name: 'Data from Removable Media', + id: 'T1025', + reference: 'https://attack.mitre.org/techniques/T1025', + tactics: ['collection'], + }, + { + name: 'Defacement', + id: 'T1491', + reference: 'https://attack.mitre.org/techniques/T1491', + tactics: ['impact'], + }, + { + name: 'Deobfuscate/Decode Files or Information', + id: 'T1140', + reference: 'https://attack.mitre.org/techniques/T1140', + tactics: ['defense-evasion'], + }, + { + name: 'Disabling Security Tools', + id: 'T1089', + reference: 'https://attack.mitre.org/techniques/T1089', + tactics: ['defense-evasion'], + }, + { + name: 'Disk Content Wipe', + id: 'T1488', + reference: 'https://attack.mitre.org/techniques/T1488', + tactics: ['impact'], + }, + { + name: 'Disk Structure Wipe', + id: 'T1487', + reference: 'https://attack.mitre.org/techniques/T1487', + tactics: ['impact'], + }, + { + name: 'Domain Fronting', + id: 'T1172', + reference: 'https://attack.mitre.org/techniques/T1172', + tactics: ['command-and-control'], + }, + { + name: 'Domain Generation Algorithms', + id: 'T1483', + reference: 'https://attack.mitre.org/techniques/T1483', + tactics: ['command-and-control'], + }, + { + name: 'Domain Trust Discovery', + id: 'T1482', + reference: 'https://attack.mitre.org/techniques/T1482', + tactics: ['discovery'], + }, + { + name: 'Drive-by Compromise', + id: 'T1189', + reference: 'https://attack.mitre.org/techniques/T1189', + tactics: ['initial-access'], + }, + { + name: 'Dylib Hijacking', + id: 'T1157', + reference: 'https://attack.mitre.org/techniques/T1157', + tactics: ['persistence', 'privilege-escalation'], + }, + { + name: 'Dynamic Data Exchange', + id: 'T1173', + reference: 'https://attack.mitre.org/techniques/T1173', + tactics: ['execution'], + }, + { + name: 'Elevated Execution with Prompt', + id: 'T1514', + reference: 'https://attack.mitre.org/techniques/T1514', + tactics: ['privilege-escalation'], + }, + { + name: 'Email Collection', + id: 'T1114', + reference: 'https://attack.mitre.org/techniques/T1114', + tactics: ['collection'], + }, + { + name: 'Emond', + id: 'T1519', + reference: 'https://attack.mitre.org/techniques/T1519', + tactics: ['persistence', 'privilege-escalation'], + }, + { + name: 'Endpoint Denial of Service', + id: 'T1499', + reference: 'https://attack.mitre.org/techniques/T1499', + tactics: ['impact'], + }, + { + name: 'Execution Guardrails', + id: 'T1480', + reference: 'https://attack.mitre.org/techniques/T1480', + tactics: ['defense-evasion'], + }, + { + name: 'Execution through API', + id: 'T1106', + reference: 'https://attack.mitre.org/techniques/T1106', + tactics: ['execution'], + }, + { + name: 'Execution through Module Load', + id: 'T1129', + reference: 'https://attack.mitre.org/techniques/T1129', + tactics: ['execution'], + }, + { + name: 'Exfiltration Over Alternative Protocol', + id: 'T1048', + reference: 'https://attack.mitre.org/techniques/T1048', + tactics: ['exfiltration'], + }, + { + name: 'Exfiltration Over Command and Control Channel', + id: 'T1041', + reference: 'https://attack.mitre.org/techniques/T1041', + tactics: ['exfiltration'], + }, + { + name: 'Exfiltration Over Other Network Medium', + id: 'T1011', + reference: 'https://attack.mitre.org/techniques/T1011', + tactics: ['exfiltration'], + }, + { + name: 'Exfiltration Over Physical Medium', + id: 'T1052', + reference: 'https://attack.mitre.org/techniques/T1052', + tactics: ['exfiltration'], + }, + { + name: 'Exploit Public-Facing Application', + id: 'T1190', + reference: 'https://attack.mitre.org/techniques/T1190', + tactics: ['initial-access'], + }, + { + name: 'Exploitation for Client Execution', + id: 'T1203', + reference: 'https://attack.mitre.org/techniques/T1203', + tactics: ['execution'], + }, + { + name: 'Exploitation for Credential Access', + id: 'T1212', + reference: 'https://attack.mitre.org/techniques/T1212', + tactics: ['credential-access'], + }, + { + name: 'Exploitation for Defense Evasion', + id: 'T1211', + reference: 'https://attack.mitre.org/techniques/T1211', + tactics: ['defense-evasion'], + }, + { + name: 'Exploitation for Privilege Escalation', + id: 'T1068', + reference: 'https://attack.mitre.org/techniques/T1068', + tactics: ['privilege-escalation'], + }, + { + name: 'Exploitation of Remote Services', + id: 'T1210', + reference: 'https://attack.mitre.org/techniques/T1210', + tactics: ['lateral-movement'], + }, + { + name: 'External Remote Services', + id: 'T1133', + reference: 'https://attack.mitre.org/techniques/T1133', + tactics: ['persistence', 'initial-access'], + }, + { + name: 'Extra Window Memory Injection', + id: 'T1181', + reference: 'https://attack.mitre.org/techniques/T1181', + tactics: ['defense-evasion', 'privilege-escalation'], + }, + { + name: 'Fallback Channels', + id: 'T1008', + reference: 'https://attack.mitre.org/techniques/T1008', + tactics: ['command-and-control'], + }, + { + name: 'File Deletion', + id: 'T1107', + reference: 'https://attack.mitre.org/techniques/T1107', + tactics: ['defense-evasion'], + }, + { + name: 'File System Logical Offsets', + id: 'T1006', + reference: 'https://attack.mitre.org/techniques/T1006', + tactics: ['defense-evasion'], + }, + { + name: 'File System Permissions Weakness', + id: 'T1044', + reference: 'https://attack.mitre.org/techniques/T1044', + tactics: ['persistence', 'privilege-escalation'], + }, + { + name: 'File and Directory Discovery', + id: 'T1083', + reference: 'https://attack.mitre.org/techniques/T1083', + tactics: ['discovery'], + }, + { + name: 'File and Directory Permissions Modification', + id: 'T1222', + reference: 'https://attack.mitre.org/techniques/T1222', + tactics: ['defense-evasion'], + }, + { + name: 'Firmware Corruption', + id: 'T1495', + reference: 'https://attack.mitre.org/techniques/T1495', + tactics: ['impact'], + }, + { + name: 'Forced Authentication', + id: 'T1187', + reference: 'https://attack.mitre.org/techniques/T1187', + tactics: ['credential-access'], + }, + { + name: 'Gatekeeper Bypass', + id: 'T1144', + reference: 'https://attack.mitre.org/techniques/T1144', + tactics: ['defense-evasion'], + }, + { + name: 'Graphical User Interface', + id: 'T1061', + reference: 'https://attack.mitre.org/techniques/T1061', + tactics: ['execution'], + }, + { + name: 'Group Policy Modification', + id: 'T1484', + reference: 'https://attack.mitre.org/techniques/T1484', + tactics: ['defense-evasion'], + }, + { + name: 'HISTCONTROL', + id: 'T1148', + reference: 'https://attack.mitre.org/techniques/T1148', + tactics: ['defense-evasion'], + }, + { + name: 'Hardware Additions', + id: 'T1200', + reference: 'https://attack.mitre.org/techniques/T1200', + tactics: ['initial-access'], + }, + { + name: 'Hidden Files and Directories', + id: 'T1158', + reference: 'https://attack.mitre.org/techniques/T1158', + tactics: ['defense-evasion', 'persistence'], + }, + { + name: 'Hidden Users', + id: 'T1147', + reference: 'https://attack.mitre.org/techniques/T1147', + tactics: ['defense-evasion'], + }, + { + name: 'Hidden Window', + id: 'T1143', + reference: 'https://attack.mitre.org/techniques/T1143', + tactics: ['defense-evasion'], + }, + { + name: 'Hooking', + id: 'T1179', + reference: 'https://attack.mitre.org/techniques/T1179', + tactics: ['persistence', 'privilege-escalation', 'credential-access'], + }, + { + name: 'Hypervisor', + id: 'T1062', + reference: 'https://attack.mitre.org/techniques/T1062', + tactics: ['persistence'], + }, + { + name: 'Image File Execution Options Injection', + id: 'T1183', + reference: 'https://attack.mitre.org/techniques/T1183', + tactics: ['privilege-escalation', 'persistence', 'defense-evasion'], + }, + { + name: 'Implant Container Image', + id: 'T1525', + reference: 'https://attack.mitre.org/techniques/T1525', + tactics: ['persistence'], + }, + { + name: 'Indicator Blocking', + id: 'T1054', + reference: 'https://attack.mitre.org/techniques/T1054', + tactics: ['defense-evasion'], + }, + { + name: 'Indicator Removal from Tools', + id: 'T1066', + reference: 'https://attack.mitre.org/techniques/T1066', + tactics: ['defense-evasion'], + }, + { + name: 'Indicator Removal on Host', + id: 'T1070', + reference: 'https://attack.mitre.org/techniques/T1070', + tactics: ['defense-evasion'], + }, + { + name: 'Indirect Command Execution', + id: 'T1202', + reference: 'https://attack.mitre.org/techniques/T1202', + tactics: ['defense-evasion'], + }, + { + name: 'Inhibit System Recovery', + id: 'T1490', + reference: 'https://attack.mitre.org/techniques/T1490', + tactics: ['impact'], + }, + { + name: 'Input Capture', + id: 'T1056', + reference: 'https://attack.mitre.org/techniques/T1056', + tactics: ['collection', 'credential-access'], + }, + { + name: 'Input Prompt', + id: 'T1141', + reference: 'https://attack.mitre.org/techniques/T1141', + tactics: ['credential-access'], + }, + { + name: 'Install Root Certificate', + id: 'T1130', + reference: 'https://attack.mitre.org/techniques/T1130', + tactics: ['defense-evasion'], + }, + { + name: 'InstallUtil', + id: 'T1118', + reference: 'https://attack.mitre.org/techniques/T1118', + tactics: ['defense-evasion', 'execution'], + }, + { + name: 'Internal Spearphishing', + id: 'T1534', + reference: 'https://attack.mitre.org/techniques/T1534', + tactics: ['lateral-movement'], + }, + { + name: 'Kerberoasting', + id: 'T1208', + reference: 'https://attack.mitre.org/techniques/T1208', + tactics: ['credential-access'], + }, + { + name: 'Kernel Modules and Extensions', + id: 'T1215', + reference: 'https://attack.mitre.org/techniques/T1215', + tactics: ['persistence'], + }, + { + name: 'Keychain', + id: 'T1142', + reference: 'https://attack.mitre.org/techniques/T1142', + tactics: ['credential-access'], + }, + { + name: 'LC_LOAD_DYLIB Addition', + id: 'T1161', + reference: 'https://attack.mitre.org/techniques/T1161', + tactics: ['persistence'], + }, + { + name: 'LC_MAIN Hijacking', + id: 'T1149', + reference: 'https://attack.mitre.org/techniques/T1149', + tactics: ['defense-evasion'], + }, + { + name: 'LLMNR/NBT-NS Poisoning and Relay', + id: 'T1171', + reference: 'https://attack.mitre.org/techniques/T1171', + tactics: ['credential-access'], + }, + { + name: 'LSASS Driver', + id: 'T1177', + reference: 'https://attack.mitre.org/techniques/T1177', + tactics: ['execution', 'persistence'], + }, + { + name: 'Launch Agent', + id: 'T1159', + reference: 'https://attack.mitre.org/techniques/T1159', + tactics: ['persistence'], + }, + { + name: 'Launch Daemon', + id: 'T1160', + reference: 'https://attack.mitre.org/techniques/T1160', + tactics: ['persistence', 'privilege-escalation'], + }, + { + name: 'Launchctl', + id: 'T1152', + reference: 'https://attack.mitre.org/techniques/T1152', + tactics: ['defense-evasion', 'execution', 'persistence'], + }, + { + name: 'Local Job Scheduling', + id: 'T1168', + reference: 'https://attack.mitre.org/techniques/T1168', + tactics: ['persistence', 'execution'], + }, + { + name: 'Login Item', + id: 'T1162', + reference: 'https://attack.mitre.org/techniques/T1162', + tactics: ['persistence'], + }, + { + name: 'Logon Scripts', + id: 'T1037', + reference: 'https://attack.mitre.org/techniques/T1037', + tactics: ['lateral-movement', 'persistence'], + }, + { + name: 'Man in the Browser', + id: 'T1185', + reference: 'https://attack.mitre.org/techniques/T1185', + tactics: ['collection'], + }, + { + name: 'Masquerading', + id: 'T1036', + reference: 'https://attack.mitre.org/techniques/T1036', + tactics: ['defense-evasion'], + }, + { + name: 'Modify Existing Service', + id: 'T1031', + reference: 'https://attack.mitre.org/techniques/T1031', + tactics: ['persistence'], + }, + { + name: 'Modify Registry', + id: 'T1112', + reference: 'https://attack.mitre.org/techniques/T1112', + tactics: ['defense-evasion'], + }, + { + name: 'Mshta', + id: 'T1170', + reference: 'https://attack.mitre.org/techniques/T1170', + tactics: ['defense-evasion', 'execution'], + }, + { + name: 'Multi-Stage Channels', + id: 'T1104', + reference: 'https://attack.mitre.org/techniques/T1104', + tactics: ['command-and-control'], + }, + { + name: 'Multi-hop Proxy', + id: 'T1188', + reference: 'https://attack.mitre.org/techniques/T1188', + tactics: ['command-and-control'], + }, + { + name: 'Multiband Communication', + id: 'T1026', + reference: 'https://attack.mitre.org/techniques/T1026', + tactics: ['command-and-control'], + }, + { + name: 'Multilayer Encryption', + id: 'T1079', + reference: 'https://attack.mitre.org/techniques/T1079', + tactics: ['command-and-control'], + }, + { + name: 'NTFS File Attributes', + id: 'T1096', + reference: 'https://attack.mitre.org/techniques/T1096', + tactics: ['defense-evasion'], + }, + { + name: 'Netsh Helper DLL', + id: 'T1128', + reference: 'https://attack.mitre.org/techniques/T1128', + tactics: ['persistence'], + }, + { + name: 'Network Denial of Service', + id: 'T1498', + reference: 'https://attack.mitre.org/techniques/T1498', + tactics: ['impact'], + }, + { + name: 'Network Service Scanning', + id: 'T1046', + reference: 'https://attack.mitre.org/techniques/T1046', + tactics: ['discovery'], + }, + { + name: 'Network Share Connection Removal', + id: 'T1126', + reference: 'https://attack.mitre.org/techniques/T1126', + tactics: ['defense-evasion'], + }, + { + name: 'Network Share Discovery', + id: 'T1135', + reference: 'https://attack.mitre.org/techniques/T1135', + tactics: ['discovery'], + }, + { + name: 'Network Sniffing', + id: 'T1040', + reference: 'https://attack.mitre.org/techniques/T1040', + tactics: ['credential-access', 'discovery'], + }, + { + name: 'New Service', + id: 'T1050', + reference: 'https://attack.mitre.org/techniques/T1050', + tactics: ['persistence', 'privilege-escalation'], + }, + { + name: 'Obfuscated Files or Information', + id: 'T1027', + reference: 'https://attack.mitre.org/techniques/T1027', + tactics: ['defense-evasion'], + }, + { + name: 'Office Application Startup', + id: 'T1137', + reference: 'https://attack.mitre.org/techniques/T1137', + tactics: ['persistence'], + }, + { + name: 'Parent PID Spoofing', + id: 'T1502', + reference: 'https://attack.mitre.org/techniques/T1502', + tactics: ['defense-evasion', 'privilege-escalation'], + }, + { + name: 'Pass the Hash', + id: 'T1075', + reference: 'https://attack.mitre.org/techniques/T1075', + tactics: ['lateral-movement'], + }, + { + name: 'Pass the Ticket', + id: 'T1097', + reference: 'https://attack.mitre.org/techniques/T1097', + tactics: ['lateral-movement'], + }, + { + name: 'Password Filter DLL', + id: 'T1174', + reference: 'https://attack.mitre.org/techniques/T1174', + tactics: ['credential-access'], + }, + { + name: 'Password Policy Discovery', + id: 'T1201', + reference: 'https://attack.mitre.org/techniques/T1201', + tactics: ['discovery'], + }, + { + name: 'Path Interception', + id: 'T1034', + reference: 'https://attack.mitre.org/techniques/T1034', + tactics: ['persistence', 'privilege-escalation'], + }, + { + name: 'Peripheral Device Discovery', + id: 'T1120', + reference: 'https://attack.mitre.org/techniques/T1120', + tactics: ['discovery'], + }, + { + name: 'Permission Groups Discovery', + id: 'T1069', + reference: 'https://attack.mitre.org/techniques/T1069', + tactics: ['discovery'], + }, + { + name: 'Plist Modification', + id: 'T1150', + reference: 'https://attack.mitre.org/techniques/T1150', + tactics: ['defense-evasion', 'persistence', 'privilege-escalation'], + }, + { + name: 'Port Knocking', + id: 'T1205', + reference: 'https://attack.mitre.org/techniques/T1205', + tactics: ['defense-evasion', 'persistence', 'command-and-control'], + }, + { + name: 'Port Monitors', + id: 'T1013', + reference: 'https://attack.mitre.org/techniques/T1013', + tactics: ['persistence', 'privilege-escalation'], + }, + { + name: 'PowerShell', + id: 'T1086', + reference: 'https://attack.mitre.org/techniques/T1086', + tactics: ['execution'], + }, + { + name: 'PowerShell Profile', + id: 'T1504', + reference: 'https://attack.mitre.org/techniques/T1504', + tactics: ['persistence', 'privilege-escalation'], + }, + { + name: 'Private Keys', + id: 'T1145', + reference: 'https://attack.mitre.org/techniques/T1145', + tactics: ['credential-access'], + }, + { + name: 'Process Discovery', + id: 'T1057', + reference: 'https://attack.mitre.org/techniques/T1057', + tactics: ['discovery'], + }, + { + name: 'Process Doppelgänging', + id: 'T1186', + reference: 'https://attack.mitre.org/techniques/T1186', + tactics: ['defense-evasion'], + }, + { + name: 'Process Hollowing', + id: 'T1093', + reference: 'https://attack.mitre.org/techniques/T1093', + tactics: ['defense-evasion'], + }, + { + name: 'Process Injection', + id: 'T1055', + reference: 'https://attack.mitre.org/techniques/T1055', + tactics: ['defense-evasion', 'privilege-escalation'], + }, + { + name: 'Query Registry', + id: 'T1012', + reference: 'https://attack.mitre.org/techniques/T1012', + tactics: ['discovery'], + }, + { + name: 'Rc.common', + id: 'T1163', + reference: 'https://attack.mitre.org/techniques/T1163', + tactics: ['persistence'], + }, + { + name: 'Re-opened Applications', + id: 'T1164', + reference: 'https://attack.mitre.org/techniques/T1164', + tactics: ['persistence'], + }, + { + name: 'Redundant Access', + id: 'T1108', + reference: 'https://attack.mitre.org/techniques/T1108', + tactics: ['defense-evasion', 'persistence'], + }, + { + name: 'Registry Run Keys / Startup Folder', + id: 'T1060', + reference: 'https://attack.mitre.org/techniques/T1060', + tactics: ['persistence'], + }, + { + name: 'Regsvcs/Regasm', + id: 'T1121', + reference: 'https://attack.mitre.org/techniques/T1121', + tactics: ['defense-evasion', 'execution'], + }, + { + name: 'Regsvr32', + id: 'T1117', + reference: 'https://attack.mitre.org/techniques/T1117', + tactics: ['defense-evasion', 'execution'], + }, + { + name: 'Remote Access Tools', + id: 'T1219', + reference: 'https://attack.mitre.org/techniques/T1219', + tactics: ['command-and-control'], + }, + { + name: 'Remote Desktop Protocol', + id: 'T1076', + reference: 'https://attack.mitre.org/techniques/T1076', + tactics: ['lateral-movement'], + }, + { + name: 'Remote File Copy', + id: 'T1105', + reference: 'https://attack.mitre.org/techniques/T1105', + tactics: ['command-and-control', 'lateral-movement'], + }, + { + name: 'Remote Services', + id: 'T1021', + reference: 'https://attack.mitre.org/techniques/T1021', + tactics: ['lateral-movement'], + }, + { + name: 'Remote System Discovery', + id: 'T1018', + reference: 'https://attack.mitre.org/techniques/T1018', + tactics: ['discovery'], + }, + { + name: 'Replication Through Removable Media', + id: 'T1091', + reference: 'https://attack.mitre.org/techniques/T1091', + tactics: ['lateral-movement', 'initial-access'], + }, + { + name: 'Resource Hijacking', + id: 'T1496', + reference: 'https://attack.mitre.org/techniques/T1496', + tactics: ['impact'], + }, + { + name: 'Revert Cloud Instance', + id: 'T1536', + reference: 'https://attack.mitre.org/techniques/T1536', + tactics: ['defense-evasion'], + }, + { + name: 'Rootkit', + id: 'T1014', + reference: 'https://attack.mitre.org/techniques/T1014', + tactics: ['defense-evasion'], + }, + { + name: 'Rundll32', + id: 'T1085', + reference: 'https://attack.mitre.org/techniques/T1085', + tactics: ['defense-evasion', 'execution'], + }, + { + name: 'Runtime Data Manipulation', + id: 'T1494', + reference: 'https://attack.mitre.org/techniques/T1494', + tactics: ['impact'], + }, + { + name: 'SID-History Injection', + id: 'T1178', + reference: 'https://attack.mitre.org/techniques/T1178', + tactics: ['privilege-escalation'], + }, + { + name: 'SIP and Trust Provider Hijacking', + id: 'T1198', + reference: 'https://attack.mitre.org/techniques/T1198', + tactics: ['defense-evasion', 'persistence'], + }, + { + name: 'SSH Hijacking', + id: 'T1184', + reference: 'https://attack.mitre.org/techniques/T1184', + tactics: ['lateral-movement'], + }, + { + name: 'Scheduled Task', + id: 'T1053', + reference: 'https://attack.mitre.org/techniques/T1053', + tactics: ['execution', 'persistence', 'privilege-escalation'], + }, + { + name: 'Scheduled Transfer', + id: 'T1029', + reference: 'https://attack.mitre.org/techniques/T1029', + tactics: ['exfiltration'], + }, + { + name: 'Screen Capture', + id: 'T1113', + reference: 'https://attack.mitre.org/techniques/T1113', + tactics: ['collection'], + }, + { + name: 'Screensaver', + id: 'T1180', + reference: 'https://attack.mitre.org/techniques/T1180', + tactics: ['persistence'], + }, + { + name: 'Scripting', + id: 'T1064', + reference: 'https://attack.mitre.org/techniques/T1064', + tactics: ['defense-evasion', 'execution'], + }, + { + name: 'Security Software Discovery', + id: 'T1063', + reference: 'https://attack.mitre.org/techniques/T1063', + tactics: ['discovery'], + }, + { + name: 'Security Support Provider', + id: 'T1101', + reference: 'https://attack.mitre.org/techniques/T1101', + tactics: ['persistence'], + }, + { + name: 'Securityd Memory', + id: 'T1167', + reference: 'https://attack.mitre.org/techniques/T1167', + tactics: ['credential-access'], + }, + { + name: 'Server Software Component', + id: 'T1505', + reference: 'https://attack.mitre.org/techniques/T1505', + tactics: ['persistence'], + }, + { + name: 'Service Execution', + id: 'T1035', + reference: 'https://attack.mitre.org/techniques/T1035', + tactics: ['execution'], + }, + { + name: 'Service Registry Permissions Weakness', + id: 'T1058', + reference: 'https://attack.mitre.org/techniques/T1058', + tactics: ['persistence', 'privilege-escalation'], + }, + { + name: 'Service Stop', + id: 'T1489', + reference: 'https://attack.mitre.org/techniques/T1489', + tactics: ['impact'], + }, + { + name: 'Setuid and Setgid', + id: 'T1166', + reference: 'https://attack.mitre.org/techniques/T1166', + tactics: ['privilege-escalation', 'persistence'], + }, + { + name: 'Shared Webroot', + id: 'T1051', + reference: 'https://attack.mitre.org/techniques/T1051', + tactics: ['lateral-movement'], + }, + { + name: 'Shortcut Modification', + id: 'T1023', + reference: 'https://attack.mitre.org/techniques/T1023', + tactics: ['persistence'], + }, + { + name: 'Signed Binary Proxy Execution', + id: 'T1218', + reference: 'https://attack.mitre.org/techniques/T1218', + tactics: ['defense-evasion', 'execution'], + }, + { + name: 'Signed Script Proxy Execution', + id: 'T1216', + reference: 'https://attack.mitre.org/techniques/T1216', + tactics: ['defense-evasion', 'execution'], + }, + { + name: 'Software Discovery', + id: 'T1518', + reference: 'https://attack.mitre.org/techniques/T1518', + tactics: ['discovery'], + }, + { + name: 'Software Packing', + id: 'T1045', + reference: 'https://attack.mitre.org/techniques/T1045', + tactics: ['defense-evasion'], + }, + { + name: 'Source', + id: 'T1153', + reference: 'https://attack.mitre.org/techniques/T1153', + tactics: ['execution'], + }, + { + name: 'Space after Filename', + id: 'T1151', + reference: 'https://attack.mitre.org/techniques/T1151', + tactics: ['defense-evasion', 'execution'], + }, + { + name: 'Spearphishing Attachment', + id: 'T1193', + reference: 'https://attack.mitre.org/techniques/T1193', + tactics: ['initial-access'], + }, + { + name: 'Spearphishing Link', + id: 'T1192', + reference: 'https://attack.mitre.org/techniques/T1192', + tactics: ['initial-access'], + }, + { + name: 'Spearphishing via Service', + id: 'T1194', + reference: 'https://attack.mitre.org/techniques/T1194', + tactics: ['initial-access'], + }, + { + name: 'Standard Application Layer Protocol', + id: 'T1071', + reference: 'https://attack.mitre.org/techniques/T1071', + tactics: ['command-and-control'], + }, + { + name: 'Standard Cryptographic Protocol', + id: 'T1032', + reference: 'https://attack.mitre.org/techniques/T1032', + tactics: ['command-and-control'], + }, + { + name: 'Standard Non-Application Layer Protocol', + id: 'T1095', + reference: 'https://attack.mitre.org/techniques/T1095', + tactics: ['command-and-control'], + }, + { + name: 'Startup Items', + id: 'T1165', + reference: 'https://attack.mitre.org/techniques/T1165', + tactics: ['persistence', 'privilege-escalation'], + }, + { + name: 'Steal Application Access Token', + id: 'T1528', + reference: 'https://attack.mitre.org/techniques/T1528', + tactics: ['credential-access'], + }, + { + name: 'Steal Web Session Cookie', + id: 'T1539', + reference: 'https://attack.mitre.org/techniques/T1539', + tactics: ['credential-access'], + }, + { + name: 'Stored Data Manipulation', + id: 'T1492', + reference: 'https://attack.mitre.org/techniques/T1492', + tactics: ['impact'], + }, + { + name: 'Sudo', + id: 'T1169', + reference: 'https://attack.mitre.org/techniques/T1169', + tactics: ['privilege-escalation'], + }, + { + name: 'Sudo Caching', + id: 'T1206', + reference: 'https://attack.mitre.org/techniques/T1206', + tactics: ['privilege-escalation'], + }, + { + name: 'Supply Chain Compromise', + id: 'T1195', + reference: 'https://attack.mitre.org/techniques/T1195', + tactics: ['initial-access'], + }, + { + name: 'System Firmware', + id: 'T1019', + reference: 'https://attack.mitre.org/techniques/T1019', + tactics: ['persistence'], + }, + { + name: 'System Information Discovery', + id: 'T1082', + reference: 'https://attack.mitre.org/techniques/T1082', + tactics: ['discovery'], + }, + { + name: 'System Network Configuration Discovery', + id: 'T1016', + reference: 'https://attack.mitre.org/techniques/T1016', + tactics: ['discovery'], + }, + { + name: 'System Network Connections Discovery', + id: 'T1049', + reference: 'https://attack.mitre.org/techniques/T1049', + tactics: ['discovery'], + }, + { + name: 'System Owner/User Discovery', + id: 'T1033', + reference: 'https://attack.mitre.org/techniques/T1033', + tactics: ['discovery'], + }, + { + name: 'System Service Discovery', + id: 'T1007', + reference: 'https://attack.mitre.org/techniques/T1007', + tactics: ['discovery'], + }, + { + name: 'System Shutdown/Reboot', + id: 'T1529', + reference: 'https://attack.mitre.org/techniques/T1529', + tactics: ['impact'], + }, + { + name: 'System Time Discovery', + id: 'T1124', + reference: 'https://attack.mitre.org/techniques/T1124', + tactics: ['discovery'], + }, + { + name: 'Systemd Service', + id: 'T1501', + reference: 'https://attack.mitre.org/techniques/T1501', + tactics: ['persistence'], + }, + { + name: 'Taint Shared Content', + id: 'T1080', + reference: 'https://attack.mitre.org/techniques/T1080', + tactics: ['lateral-movement'], + }, + { + name: 'Template Injection', + id: 'T1221', + reference: 'https://attack.mitre.org/techniques/T1221', + tactics: ['defense-evasion'], + }, + { + name: 'Third-party Software', + id: 'T1072', + reference: 'https://attack.mitre.org/techniques/T1072', + tactics: ['execution', 'lateral-movement'], + }, + { + name: 'Time Providers', + id: 'T1209', + reference: 'https://attack.mitre.org/techniques/T1209', + tactics: ['persistence'], + }, + { + name: 'Timestomp', + id: 'T1099', + reference: 'https://attack.mitre.org/techniques/T1099', + tactics: ['defense-evasion'], + }, + { + name: 'Transfer Data to Cloud Account', + id: 'T1537', + reference: 'https://attack.mitre.org/techniques/T1537', + tactics: ['exfiltration'], + }, + { + name: 'Transmitted Data Manipulation', + id: 'T1493', + reference: 'https://attack.mitre.org/techniques/T1493', + tactics: ['impact'], + }, + { + name: 'Trap', + id: 'T1154', + reference: 'https://attack.mitre.org/techniques/T1154', + tactics: ['execution', 'persistence'], + }, + { + name: 'Trusted Developer Utilities', + id: 'T1127', + reference: 'https://attack.mitre.org/techniques/T1127', + tactics: ['defense-evasion', 'execution'], + }, + { + name: 'Trusted Relationship', + id: 'T1199', + reference: 'https://attack.mitre.org/techniques/T1199', + tactics: ['initial-access'], + }, + { + name: 'Two-Factor Authentication Interception', + id: 'T1111', + reference: 'https://attack.mitre.org/techniques/T1111', + tactics: ['credential-access'], + }, + { + name: 'Uncommonly Used Port', + id: 'T1065', + reference: 'https://attack.mitre.org/techniques/T1065', + tactics: ['command-and-control'], + }, + { + name: 'Unused/Unsupported Cloud Regions', + id: 'T1535', + reference: 'https://attack.mitre.org/techniques/T1535', + tactics: ['defense-evasion'], + }, + { + name: 'User Execution', + id: 'T1204', + reference: 'https://attack.mitre.org/techniques/T1204', + tactics: ['execution'], + }, + { + name: 'Valid Accounts', + id: 'T1078', + reference: 'https://attack.mitre.org/techniques/T1078', + tactics: ['defense-evasion', 'persistence', 'privilege-escalation', 'initial-access'], + }, + { + name: 'Video Capture', + id: 'T1125', + reference: 'https://attack.mitre.org/techniques/T1125', + tactics: ['collection'], + }, + { + name: 'Virtualization/Sandbox Evasion', + id: 'T1497', + reference: 'https://attack.mitre.org/techniques/T1497', + tactics: ['defense-evasion', 'discovery'], + }, + { + name: 'Web Service', + id: 'T1102', + reference: 'https://attack.mitre.org/techniques/T1102', + tactics: ['command-and-control', 'defense-evasion'], + }, + { + name: 'Web Session Cookie', + id: 'T1506', + reference: 'https://attack.mitre.org/techniques/T1506', + tactics: ['defense-evasion', 'lateral-movement'], + }, + { + name: 'Web Shell', + id: 'T1100', + reference: 'https://attack.mitre.org/techniques/T1100', + tactics: ['persistence', 'privilege-escalation'], + }, + { + name: 'Windows Admin Shares', + id: 'T1077', + reference: 'https://attack.mitre.org/techniques/T1077', + tactics: ['lateral-movement'], + }, + { + name: 'Windows Management Instrumentation', + id: 'T1047', + reference: 'https://attack.mitre.org/techniques/T1047', + tactics: ['execution'], + }, + { + name: 'Windows Management Instrumentation Event Subscription', + id: 'T1084', + reference: 'https://attack.mitre.org/techniques/T1084', + tactics: ['persistence'], + }, + { + name: 'Windows Remote Management', + id: 'T1028', + reference: 'https://attack.mitre.org/techniques/T1028', + tactics: ['execution', 'lateral-movement'], + }, + { + name: 'Winlogon Helper DLL', + id: 'T1004', + reference: 'https://attack.mitre.org/techniques/T1004', + tactics: ['persistence'], + }, + { + name: 'XSL Script Processing', + id: 'T1220', + reference: 'https://attack.mitre.org/techniques/T1220', + tactics: ['defense-evasion', 'execution'], + }, +]; + +export const techniquesOptions: MitreTechniquesOptions[] = [ + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.bashProfileAndBashrcDescription', + { defaultMessage: '.bash_profile and .bashrc (T1156)' } + ), + id: 'T1156', + name: '.bash_profile and .bashrc', + reference: 'https://attack.mitre.org/techniques/T1156', + tactics: 'persistence', + value: 'bashProfileAndBashrc', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.accessTokenManipulationDescription', + { defaultMessage: 'Access Token Manipulation (T1134)' } + ), + id: 'T1134', + name: 'Access Token Manipulation', + reference: 'https://attack.mitre.org/techniques/T1134', + tactics: 'defense-evasion,privilege-escalation', + value: 'accessTokenManipulation', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.accessibilityFeaturesDescription', + { defaultMessage: 'Accessibility Features (T1015)' } + ), + id: 'T1015', + name: 'Accessibility Features', + reference: 'https://attack.mitre.org/techniques/T1015', + tactics: 'persistence,privilege-escalation', + value: 'accessibilityFeatures', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.accountAccessRemovalDescription', + { defaultMessage: 'Account Access Removal (T1531)' } + ), + id: 'T1531', + name: 'Account Access Removal', + reference: 'https://attack.mitre.org/techniques/T1531', + tactics: 'impact', + value: 'accountAccessRemoval', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.accountDiscoveryDescription', + { defaultMessage: 'Account Discovery (T1087)' } + ), + id: 'T1087', + name: 'Account Discovery', + reference: 'https://attack.mitre.org/techniques/T1087', + tactics: 'discovery', + value: 'accountDiscovery', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.accountManipulationDescription', + { defaultMessage: 'Account Manipulation (T1098)' } + ), + id: 'T1098', + name: 'Account Manipulation', + reference: 'https://attack.mitre.org/techniques/T1098', + tactics: 'credential-access,persistence', + value: 'accountManipulation', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.appCertDlLsDescription', + { defaultMessage: 'AppCert DLLs (T1182)' } + ), + id: 'T1182', + name: 'AppCert DLLs', + reference: 'https://attack.mitre.org/techniques/T1182', + tactics: 'persistence,privilege-escalation', + value: 'appCertDlLs', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.appInitDlLsDescription', + { defaultMessage: 'AppInit DLLs (T1103)' } + ), + id: 'T1103', + name: 'AppInit DLLs', + reference: 'https://attack.mitre.org/techniques/T1103', + tactics: 'persistence,privilege-escalation', + value: 'appInitDlLs', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.appleScriptDescription', + { defaultMessage: 'AppleScript (T1155)' } + ), + id: 'T1155', + name: 'AppleScript', + reference: 'https://attack.mitre.org/techniques/T1155', + tactics: 'execution,lateral-movement', + value: 'appleScript', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.applicationAccessTokenDescription', + { defaultMessage: 'Application Access Token (T1527)' } + ), + id: 'T1527', + name: 'Application Access Token', + reference: 'https://attack.mitre.org/techniques/T1527', + tactics: 'defense-evasion,lateral-movement', + value: 'applicationAccessToken', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.applicationDeploymentSoftwareDescription', + { defaultMessage: 'Application Deployment Software (T1017)' } + ), + id: 'T1017', + name: 'Application Deployment Software', + reference: 'https://attack.mitre.org/techniques/T1017', + tactics: 'lateral-movement', + value: 'applicationDeploymentSoftware', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.applicationShimmingDescription', + { defaultMessage: 'Application Shimming (T1138)' } + ), + id: 'T1138', + name: 'Application Shimming', + reference: 'https://attack.mitre.org/techniques/T1138', + tactics: 'persistence,privilege-escalation', + value: 'applicationShimming', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.applicationWindowDiscoveryDescription', + { defaultMessage: 'Application Window Discovery (T1010)' } + ), + id: 'T1010', + name: 'Application Window Discovery', + reference: 'https://attack.mitre.org/techniques/T1010', + tactics: 'discovery', + value: 'applicationWindowDiscovery', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.audioCaptureDescription', + { defaultMessage: 'Audio Capture (T1123)' } + ), + id: 'T1123', + name: 'Audio Capture', + reference: 'https://attack.mitre.org/techniques/T1123', + tactics: 'collection', + value: 'audioCapture', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.authenticationPackageDescription', + { defaultMessage: 'Authentication Package (T1131)' } + ), + id: 'T1131', + name: 'Authentication Package', + reference: 'https://attack.mitre.org/techniques/T1131', + tactics: 'persistence', + value: 'authenticationPackage', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.automatedCollectionDescription', + { defaultMessage: 'Automated Collection (T1119)' } + ), + id: 'T1119', + name: 'Automated Collection', + reference: 'https://attack.mitre.org/techniques/T1119', + tactics: 'collection', + value: 'automatedCollection', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.automatedExfiltrationDescription', + { defaultMessage: 'Automated Exfiltration (T1020)' } + ), + id: 'T1020', + name: 'Automated Exfiltration', + reference: 'https://attack.mitre.org/techniques/T1020', + tactics: 'exfiltration', + value: 'automatedExfiltration', + }, + { + label: i18n.translate('xpack.siem.detectionEngine.mitreAttackTechniques.bitsJobsDescription', { + defaultMessage: 'BITS Jobs (T1197)', + }), + id: 'T1197', + name: 'BITS Jobs', + reference: 'https://attack.mitre.org/techniques/T1197', + tactics: 'defense-evasion,persistence', + value: 'bitsJobs', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.bashHistoryDescription', + { defaultMessage: 'Bash History (T1139)' } + ), + id: 'T1139', + name: 'Bash History', + reference: 'https://attack.mitre.org/techniques/T1139', + tactics: 'credential-access', + value: 'bashHistory', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.binaryPaddingDescription', + { defaultMessage: 'Binary Padding (T1009)' } + ), + id: 'T1009', + name: 'Binary Padding', + reference: 'https://attack.mitre.org/techniques/T1009', + tactics: 'defense-evasion', + value: 'binaryPadding', + }, + { + label: i18n.translate('xpack.siem.detectionEngine.mitreAttackTechniques.bootkitDescription', { + defaultMessage: 'Bootkit (T1067)', + }), + id: 'T1067', + name: 'Bootkit', + reference: 'https://attack.mitre.org/techniques/T1067', + tactics: 'persistence', + value: 'bootkit', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.browserBookmarkDiscoveryDescription', + { defaultMessage: 'Browser Bookmark Discovery (T1217)' } + ), + id: 'T1217', + name: 'Browser Bookmark Discovery', + reference: 'https://attack.mitre.org/techniques/T1217', + tactics: 'discovery', + value: 'browserBookmarkDiscovery', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.browserExtensionsDescription', + { defaultMessage: 'Browser Extensions (T1176)' } + ), + id: 'T1176', + name: 'Browser Extensions', + reference: 'https://attack.mitre.org/techniques/T1176', + tactics: 'persistence', + value: 'browserExtensions', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.bruteForceDescription', + { defaultMessage: 'Brute Force (T1110)' } + ), + id: 'T1110', + name: 'Brute Force', + reference: 'https://attack.mitre.org/techniques/T1110', + tactics: 'credential-access', + value: 'bruteForce', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.bypassUserAccountControlDescription', + { defaultMessage: 'Bypass User Account Control (T1088)' } + ), + id: 'T1088', + name: 'Bypass User Account Control', + reference: 'https://attack.mitre.org/techniques/T1088', + tactics: 'defense-evasion,privilege-escalation', + value: 'bypassUserAccountControl', + }, + { + label: i18n.translate('xpack.siem.detectionEngine.mitreAttackTechniques.cmstpDescription', { + defaultMessage: 'CMSTP (T1191)', + }), + id: 'T1191', + name: 'CMSTP', + reference: 'https://attack.mitre.org/techniques/T1191', + tactics: 'defense-evasion,execution', + value: 'cmstp', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.changeDefaultFileAssociationDescription', + { defaultMessage: 'Change Default File Association (T1042)' } + ), + id: 'T1042', + name: 'Change Default File Association', + reference: 'https://attack.mitre.org/techniques/T1042', + tactics: 'persistence', + value: 'changeDefaultFileAssociation', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.clearCommandHistoryDescription', + { defaultMessage: 'Clear Command History (T1146)' } + ), + id: 'T1146', + name: 'Clear Command History', + reference: 'https://attack.mitre.org/techniques/T1146', + tactics: 'defense-evasion', + value: 'clearCommandHistory', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.clipboardDataDescription', + { defaultMessage: 'Clipboard Data (T1115)' } + ), + id: 'T1115', + name: 'Clipboard Data', + reference: 'https://attack.mitre.org/techniques/T1115', + tactics: 'collection', + value: 'clipboardData', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.cloudInstanceMetadataApiDescription', + { defaultMessage: 'Cloud Instance Metadata API (T1522)' } + ), + id: 'T1522', + name: 'Cloud Instance Metadata API', + reference: 'https://attack.mitre.org/techniques/T1522', + tactics: 'credential-access', + value: 'cloudInstanceMetadataApi', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.cloudServiceDashboardDescription', + { defaultMessage: 'Cloud Service Dashboard (T1538)' } + ), + id: 'T1538', + name: 'Cloud Service Dashboard', + reference: 'https://attack.mitre.org/techniques/T1538', + tactics: 'discovery', + value: 'cloudServiceDashboard', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.cloudServiceDiscoveryDescription', + { defaultMessage: 'Cloud Service Discovery (T1526)' } + ), + id: 'T1526', + name: 'Cloud Service Discovery', + reference: 'https://attack.mitre.org/techniques/T1526', + tactics: 'discovery', + value: 'cloudServiceDiscovery', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.codeSigningDescription', + { defaultMessage: 'Code Signing (T1116)' } + ), + id: 'T1116', + name: 'Code Signing', + reference: 'https://attack.mitre.org/techniques/T1116', + tactics: 'defense-evasion', + value: 'codeSigning', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.commandLineInterfaceDescription', + { defaultMessage: 'Command-Line Interface (T1059)' } + ), + id: 'T1059', + name: 'Command-Line Interface', + reference: 'https://attack.mitre.org/techniques/T1059', + tactics: 'execution', + value: 'commandLineInterface', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.commonlyUsedPortDescription', + { defaultMessage: 'Commonly Used Port (T1043)' } + ), + id: 'T1043', + name: 'Commonly Used Port', + reference: 'https://attack.mitre.org/techniques/T1043', + tactics: 'command-and-control', + value: 'commonlyUsedPort', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.communicationThroughRemovableMediaDescription', + { defaultMessage: 'Communication Through Removable Media (T1092)' } + ), + id: 'T1092', + name: 'Communication Through Removable Media', + reference: 'https://attack.mitre.org/techniques/T1092', + tactics: 'command-and-control', + value: 'communicationThroughRemovableMedia', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.compileAfterDeliveryDescription', + { defaultMessage: 'Compile After Delivery (T1500)' } + ), + id: 'T1500', + name: 'Compile After Delivery', + reference: 'https://attack.mitre.org/techniques/T1500', + tactics: 'defense-evasion', + value: 'compileAfterDelivery', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.compiledHtmlFileDescription', + { defaultMessage: 'Compiled HTML File (T1223)' } + ), + id: 'T1223', + name: 'Compiled HTML File', + reference: 'https://attack.mitre.org/techniques/T1223', + tactics: 'defense-evasion,execution', + value: 'compiledHtmlFile', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.componentFirmwareDescription', + { defaultMessage: 'Component Firmware (T1109)' } + ), + id: 'T1109', + name: 'Component Firmware', + reference: 'https://attack.mitre.org/techniques/T1109', + tactics: 'defense-evasion,persistence', + value: 'componentFirmware', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.componentObjectModelHijackingDescription', + { defaultMessage: 'Component Object Model Hijacking (T1122)' } + ), + id: 'T1122', + name: 'Component Object Model Hijacking', + reference: 'https://attack.mitre.org/techniques/T1122', + tactics: 'defense-evasion,persistence', + value: 'componentObjectModelHijacking', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.componentObjectModelAndDistributedComDescription', + { defaultMessage: 'Component Object Model and Distributed COM (T1175)' } + ), + id: 'T1175', + name: 'Component Object Model and Distributed COM', + reference: 'https://attack.mitre.org/techniques/T1175', + tactics: 'lateral-movement,execution', + value: 'componentObjectModelAndDistributedCom', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.connectionProxyDescription', + { defaultMessage: 'Connection Proxy (T1090)' } + ), + id: 'T1090', + name: 'Connection Proxy', + reference: 'https://attack.mitre.org/techniques/T1090', + tactics: 'command-and-control,defense-evasion', + value: 'connectionProxy', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.controlPanelItemsDescription', + { defaultMessage: 'Control Panel Items (T1196)' } + ), + id: 'T1196', + name: 'Control Panel Items', + reference: 'https://attack.mitre.org/techniques/T1196', + tactics: 'defense-evasion,execution', + value: 'controlPanelItems', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.createAccountDescription', + { defaultMessage: 'Create Account (T1136)' } + ), + id: 'T1136', + name: 'Create Account', + reference: 'https://attack.mitre.org/techniques/T1136', + tactics: 'persistence', + value: 'createAccount', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.credentialDumpingDescription', + { defaultMessage: 'Credential Dumping (T1003)' } + ), + id: 'T1003', + name: 'Credential Dumping', + reference: 'https://attack.mitre.org/techniques/T1003', + tactics: 'credential-access', + value: 'credentialDumping', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.credentialsFromWebBrowsersDescription', + { defaultMessage: 'Credentials from Web Browsers (T1503)' } + ), + id: 'T1503', + name: 'Credentials from Web Browsers', + reference: 'https://attack.mitre.org/techniques/T1503', + tactics: 'credential-access', + value: 'credentialsFromWebBrowsers', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.credentialsInFilesDescription', + { defaultMessage: 'Credentials in Files (T1081)' } + ), + id: 'T1081', + name: 'Credentials in Files', + reference: 'https://attack.mitre.org/techniques/T1081', + tactics: 'credential-access', + value: 'credentialsInFiles', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.credentialsInRegistryDescription', + { defaultMessage: 'Credentials in Registry (T1214)' } + ), + id: 'T1214', + name: 'Credentials in Registry', + reference: 'https://attack.mitre.org/techniques/T1214', + tactics: 'credential-access', + value: 'credentialsInRegistry', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.customCommandAndControlProtocolDescription', + { defaultMessage: 'Custom Command and Control Protocol (T1094)' } + ), + id: 'T1094', + name: 'Custom Command and Control Protocol', + reference: 'https://attack.mitre.org/techniques/T1094', + tactics: 'command-and-control', + value: 'customCommandAndControlProtocol', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.customCryptographicProtocolDescription', + { defaultMessage: 'Custom Cryptographic Protocol (T1024)' } + ), + id: 'T1024', + name: 'Custom Cryptographic Protocol', + reference: 'https://attack.mitre.org/techniques/T1024', + tactics: 'command-and-control', + value: 'customCryptographicProtocol', + }, + { + label: i18n.translate('xpack.siem.detectionEngine.mitreAttackTechniques.dcShadowDescription', { + defaultMessage: 'DCShadow (T1207)', + }), + id: 'T1207', + name: 'DCShadow', + reference: 'https://attack.mitre.org/techniques/T1207', + tactics: 'defense-evasion', + value: 'dcShadow', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.dllSearchOrderHijackingDescription', + { defaultMessage: 'DLL Search Order Hijacking (T1038)' } + ), + id: 'T1038', + name: 'DLL Search Order Hijacking', + reference: 'https://attack.mitre.org/techniques/T1038', + tactics: 'persistence,privilege-escalation,defense-evasion', + value: 'dllSearchOrderHijacking', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.dllSideLoadingDescription', + { defaultMessage: 'DLL Side-Loading (T1073)' } + ), + id: 'T1073', + name: 'DLL Side-Loading', + reference: 'https://attack.mitre.org/techniques/T1073', + tactics: 'defense-evasion', + value: 'dllSideLoading', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.dataCompressedDescription', + { defaultMessage: 'Data Compressed (T1002)' } + ), + id: 'T1002', + name: 'Data Compressed', + reference: 'https://attack.mitre.org/techniques/T1002', + tactics: 'exfiltration', + value: 'dataCompressed', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.dataDestructionDescription', + { defaultMessage: 'Data Destruction (T1485)' } + ), + id: 'T1485', + name: 'Data Destruction', + reference: 'https://attack.mitre.org/techniques/T1485', + tactics: 'impact', + value: 'dataDestruction', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.dataEncodingDescription', + { defaultMessage: 'Data Encoding (T1132)' } + ), + id: 'T1132', + name: 'Data Encoding', + reference: 'https://attack.mitre.org/techniques/T1132', + tactics: 'command-and-control', + value: 'dataEncoding', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.dataEncryptedDescription', + { defaultMessage: 'Data Encrypted (T1022)' } + ), + id: 'T1022', + name: 'Data Encrypted', + reference: 'https://attack.mitre.org/techniques/T1022', + tactics: 'exfiltration', + value: 'dataEncrypted', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.dataEncryptedForImpactDescription', + { defaultMessage: 'Data Encrypted for Impact (T1486)' } + ), + id: 'T1486', + name: 'Data Encrypted for Impact', + reference: 'https://attack.mitre.org/techniques/T1486', + tactics: 'impact', + value: 'dataEncryptedForImpact', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.dataObfuscationDescription', + { defaultMessage: 'Data Obfuscation (T1001)' } + ), + id: 'T1001', + name: 'Data Obfuscation', + reference: 'https://attack.mitre.org/techniques/T1001', + tactics: 'command-and-control', + value: 'dataObfuscation', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.dataStagedDescription', + { defaultMessage: 'Data Staged (T1074)' } + ), + id: 'T1074', + name: 'Data Staged', + reference: 'https://attack.mitre.org/techniques/T1074', + tactics: 'collection', + value: 'dataStaged', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.dataTransferSizeLimitsDescription', + { defaultMessage: 'Data Transfer Size Limits (T1030)' } + ), + id: 'T1030', + name: 'Data Transfer Size Limits', + reference: 'https://attack.mitre.org/techniques/T1030', + tactics: 'exfiltration', + value: 'dataTransferSizeLimits', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.dataFromCloudStorageObjectDescription', + { defaultMessage: 'Data from Cloud Storage Object (T1530)' } + ), + id: 'T1530', + name: 'Data from Cloud Storage Object', + reference: 'https://attack.mitre.org/techniques/T1530', + tactics: 'collection', + value: 'dataFromCloudStorageObject', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.dataFromInformationRepositoriesDescription', + { defaultMessage: 'Data from Information Repositories (T1213)' } + ), + id: 'T1213', + name: 'Data from Information Repositories', + reference: 'https://attack.mitre.org/techniques/T1213', + tactics: 'collection', + value: 'dataFromInformationRepositories', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.dataFromLocalSystemDescription', + { defaultMessage: 'Data from Local System (T1005)' } + ), + id: 'T1005', + name: 'Data from Local System', + reference: 'https://attack.mitre.org/techniques/T1005', + tactics: 'collection', + value: 'dataFromLocalSystem', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.dataFromNetworkSharedDriveDescription', + { defaultMessage: 'Data from Network Shared Drive (T1039)' } + ), + id: 'T1039', + name: 'Data from Network Shared Drive', + reference: 'https://attack.mitre.org/techniques/T1039', + tactics: 'collection', + value: 'dataFromNetworkSharedDrive', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.dataFromRemovableMediaDescription', + { defaultMessage: 'Data from Removable Media (T1025)' } + ), + id: 'T1025', + name: 'Data from Removable Media', + reference: 'https://attack.mitre.org/techniques/T1025', + tactics: 'collection', + value: 'dataFromRemovableMedia', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.defacementDescription', + { defaultMessage: 'Defacement (T1491)' } + ), + id: 'T1491', + name: 'Defacement', + reference: 'https://attack.mitre.org/techniques/T1491', + tactics: 'impact', + value: 'defacement', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.deobfuscateDecodeFilesOrInformationDescription', + { defaultMessage: 'Deobfuscate/Decode Files or Information (T1140)' } + ), + id: 'T1140', + name: 'Deobfuscate/Decode Files or Information', + reference: 'https://attack.mitre.org/techniques/T1140', + tactics: 'defense-evasion', + value: 'deobfuscateDecodeFilesOrInformation', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.disablingSecurityToolsDescription', + { defaultMessage: 'Disabling Security Tools (T1089)' } + ), + id: 'T1089', + name: 'Disabling Security Tools', + reference: 'https://attack.mitre.org/techniques/T1089', + tactics: 'defense-evasion', + value: 'disablingSecurityTools', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.diskContentWipeDescription', + { defaultMessage: 'Disk Content Wipe (T1488)' } + ), + id: 'T1488', + name: 'Disk Content Wipe', + reference: 'https://attack.mitre.org/techniques/T1488', + tactics: 'impact', + value: 'diskContentWipe', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.diskStructureWipeDescription', + { defaultMessage: 'Disk Structure Wipe (T1487)' } + ), + id: 'T1487', + name: 'Disk Structure Wipe', + reference: 'https://attack.mitre.org/techniques/T1487', + tactics: 'impact', + value: 'diskStructureWipe', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.domainFrontingDescription', + { defaultMessage: 'Domain Fronting (T1172)' } + ), + id: 'T1172', + name: 'Domain Fronting', + reference: 'https://attack.mitre.org/techniques/T1172', + tactics: 'command-and-control', + value: 'domainFronting', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.domainGenerationAlgorithmsDescription', + { defaultMessage: 'Domain Generation Algorithms (T1483)' } + ), + id: 'T1483', + name: 'Domain Generation Algorithms', + reference: 'https://attack.mitre.org/techniques/T1483', + tactics: 'command-and-control', + value: 'domainGenerationAlgorithms', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.domainTrustDiscoveryDescription', + { defaultMessage: 'Domain Trust Discovery (T1482)' } + ), + id: 'T1482', + name: 'Domain Trust Discovery', + reference: 'https://attack.mitre.org/techniques/T1482', + tactics: 'discovery', + value: 'domainTrustDiscovery', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.driveByCompromiseDescription', + { defaultMessage: 'Drive-by Compromise (T1189)' } + ), + id: 'T1189', + name: 'Drive-by Compromise', + reference: 'https://attack.mitre.org/techniques/T1189', + tactics: 'initial-access', + value: 'driveByCompromise', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.dylibHijackingDescription', + { defaultMessage: 'Dylib Hijacking (T1157)' } + ), + id: 'T1157', + name: 'Dylib Hijacking', + reference: 'https://attack.mitre.org/techniques/T1157', + tactics: 'persistence,privilege-escalation', + value: 'dylibHijacking', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.dynamicDataExchangeDescription', + { defaultMessage: 'Dynamic Data Exchange (T1173)' } + ), + id: 'T1173', + name: 'Dynamic Data Exchange', + reference: 'https://attack.mitre.org/techniques/T1173', + tactics: 'execution', + value: 'dynamicDataExchange', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.elevatedExecutionWithPromptDescription', + { defaultMessage: 'Elevated Execution with Prompt (T1514)' } + ), + id: 'T1514', + name: 'Elevated Execution with Prompt', + reference: 'https://attack.mitre.org/techniques/T1514', + tactics: 'privilege-escalation', + value: 'elevatedExecutionWithPrompt', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.emailCollectionDescription', + { defaultMessage: 'Email Collection (T1114)' } + ), + id: 'T1114', + name: 'Email Collection', + reference: 'https://attack.mitre.org/techniques/T1114', + tactics: 'collection', + value: 'emailCollection', + }, + { + label: i18n.translate('xpack.siem.detectionEngine.mitreAttackTechniques.emondDescription', { + defaultMessage: 'Emond (T1519)', + }), + id: 'T1519', + name: 'Emond', + reference: 'https://attack.mitre.org/techniques/T1519', + tactics: 'persistence,privilege-escalation', + value: 'emond', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.endpointDenialOfServiceDescription', + { defaultMessage: 'Endpoint Denial of Service (T1499)' } + ), + id: 'T1499', + name: 'Endpoint Denial of Service', + reference: 'https://attack.mitre.org/techniques/T1499', + tactics: 'impact', + value: 'endpointDenialOfService', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.executionGuardrailsDescription', + { defaultMessage: 'Execution Guardrails (T1480)' } + ), + id: 'T1480', + name: 'Execution Guardrails', + reference: 'https://attack.mitre.org/techniques/T1480', + tactics: 'defense-evasion', + value: 'executionGuardrails', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.executionThroughApiDescription', + { defaultMessage: 'Execution through API (T1106)' } + ), + id: 'T1106', + name: 'Execution through API', + reference: 'https://attack.mitre.org/techniques/T1106', + tactics: 'execution', + value: 'executionThroughApi', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.executionThroughModuleLoadDescription', + { defaultMessage: 'Execution through Module Load (T1129)' } + ), + id: 'T1129', + name: 'Execution through Module Load', + reference: 'https://attack.mitre.org/techniques/T1129', + tactics: 'execution', + value: 'executionThroughModuleLoad', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.exfiltrationOverAlternativeProtocolDescription', + { defaultMessage: 'Exfiltration Over Alternative Protocol (T1048)' } + ), + id: 'T1048', + name: 'Exfiltration Over Alternative Protocol', + reference: 'https://attack.mitre.org/techniques/T1048', + tactics: 'exfiltration', + value: 'exfiltrationOverAlternativeProtocol', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.exfiltrationOverCommandAndControlChannelDescription', + { defaultMessage: 'Exfiltration Over Command and Control Channel (T1041)' } + ), + id: 'T1041', + name: 'Exfiltration Over Command and Control Channel', + reference: 'https://attack.mitre.org/techniques/T1041', + tactics: 'exfiltration', + value: 'exfiltrationOverCommandAndControlChannel', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.exfiltrationOverOtherNetworkMediumDescription', + { defaultMessage: 'Exfiltration Over Other Network Medium (T1011)' } + ), + id: 'T1011', + name: 'Exfiltration Over Other Network Medium', + reference: 'https://attack.mitre.org/techniques/T1011', + tactics: 'exfiltration', + value: 'exfiltrationOverOtherNetworkMedium', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.exfiltrationOverPhysicalMediumDescription', + { defaultMessage: 'Exfiltration Over Physical Medium (T1052)' } + ), + id: 'T1052', + name: 'Exfiltration Over Physical Medium', + reference: 'https://attack.mitre.org/techniques/T1052', + tactics: 'exfiltration', + value: 'exfiltrationOverPhysicalMedium', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.exploitPublicFacingApplicationDescription', + { defaultMessage: 'Exploit Public-Facing Application (T1190)' } + ), + id: 'T1190', + name: 'Exploit Public-Facing Application', + reference: 'https://attack.mitre.org/techniques/T1190', + tactics: 'initial-access', + value: 'exploitPublicFacingApplication', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.exploitationForClientExecutionDescription', + { defaultMessage: 'Exploitation for Client Execution (T1203)' } + ), + id: 'T1203', + name: 'Exploitation for Client Execution', + reference: 'https://attack.mitre.org/techniques/T1203', + tactics: 'execution', + value: 'exploitationForClientExecution', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.exploitationForCredentialAccessDescription', + { defaultMessage: 'Exploitation for Credential Access (T1212)' } + ), + id: 'T1212', + name: 'Exploitation for Credential Access', + reference: 'https://attack.mitre.org/techniques/T1212', + tactics: 'credential-access', + value: 'exploitationForCredentialAccess', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.exploitationForDefenseEvasionDescription', + { defaultMessage: 'Exploitation for Defense Evasion (T1211)' } + ), + id: 'T1211', + name: 'Exploitation for Defense Evasion', + reference: 'https://attack.mitre.org/techniques/T1211', + tactics: 'defense-evasion', + value: 'exploitationForDefenseEvasion', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.exploitationForPrivilegeEscalationDescription', + { defaultMessage: 'Exploitation for Privilege Escalation (T1068)' } + ), + id: 'T1068', + name: 'Exploitation for Privilege Escalation', + reference: 'https://attack.mitre.org/techniques/T1068', + tactics: 'privilege-escalation', + value: 'exploitationForPrivilegeEscalation', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.exploitationOfRemoteServicesDescription', + { defaultMessage: 'Exploitation of Remote Services (T1210)' } + ), + id: 'T1210', + name: 'Exploitation of Remote Services', + reference: 'https://attack.mitre.org/techniques/T1210', + tactics: 'lateral-movement', + value: 'exploitationOfRemoteServices', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.externalRemoteServicesDescription', + { defaultMessage: 'External Remote Services (T1133)' } + ), + id: 'T1133', + name: 'External Remote Services', + reference: 'https://attack.mitre.org/techniques/T1133', + tactics: 'persistence,initial-access', + value: 'externalRemoteServices', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.extraWindowMemoryInjectionDescription', + { defaultMessage: 'Extra Window Memory Injection (T1181)' } + ), + id: 'T1181', + name: 'Extra Window Memory Injection', + reference: 'https://attack.mitre.org/techniques/T1181', + tactics: 'defense-evasion,privilege-escalation', + value: 'extraWindowMemoryInjection', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.fallbackChannelsDescription', + { defaultMessage: 'Fallback Channels (T1008)' } + ), + id: 'T1008', + name: 'Fallback Channels', + reference: 'https://attack.mitre.org/techniques/T1008', + tactics: 'command-and-control', + value: 'fallbackChannels', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.fileDeletionDescription', + { defaultMessage: 'File Deletion (T1107)' } + ), + id: 'T1107', + name: 'File Deletion', + reference: 'https://attack.mitre.org/techniques/T1107', + tactics: 'defense-evasion', + value: 'fileDeletion', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.fileSystemLogicalOffsetsDescription', + { defaultMessage: 'File System Logical Offsets (T1006)' } + ), + id: 'T1006', + name: 'File System Logical Offsets', + reference: 'https://attack.mitre.org/techniques/T1006', + tactics: 'defense-evasion', + value: 'fileSystemLogicalOffsets', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.fileSystemPermissionsWeaknessDescription', + { defaultMessage: 'File System Permissions Weakness (T1044)' } + ), + id: 'T1044', + name: 'File System Permissions Weakness', + reference: 'https://attack.mitre.org/techniques/T1044', + tactics: 'persistence,privilege-escalation', + value: 'fileSystemPermissionsWeakness', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.fileAndDirectoryDiscoveryDescription', + { defaultMessage: 'File and Directory Discovery (T1083)' } + ), + id: 'T1083', + name: 'File and Directory Discovery', + reference: 'https://attack.mitre.org/techniques/T1083', + tactics: 'discovery', + value: 'fileAndDirectoryDiscovery', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.fileAndDirectoryPermissionsModificationDescription', + { defaultMessage: 'File and Directory Permissions Modification (T1222)' } + ), + id: 'T1222', + name: 'File and Directory Permissions Modification', + reference: 'https://attack.mitre.org/techniques/T1222', + tactics: 'defense-evasion', + value: 'fileAndDirectoryPermissionsModification', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.firmwareCorruptionDescription', + { defaultMessage: 'Firmware Corruption (T1495)' } + ), + id: 'T1495', + name: 'Firmware Corruption', + reference: 'https://attack.mitre.org/techniques/T1495', + tactics: 'impact', + value: 'firmwareCorruption', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.forcedAuthenticationDescription', + { defaultMessage: 'Forced Authentication (T1187)' } + ), + id: 'T1187', + name: 'Forced Authentication', + reference: 'https://attack.mitre.org/techniques/T1187', + tactics: 'credential-access', + value: 'forcedAuthentication', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.gatekeeperBypassDescription', + { defaultMessage: 'Gatekeeper Bypass (T1144)' } + ), + id: 'T1144', + name: 'Gatekeeper Bypass', + reference: 'https://attack.mitre.org/techniques/T1144', + tactics: 'defense-evasion', + value: 'gatekeeperBypass', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.graphicalUserInterfaceDescription', + { defaultMessage: 'Graphical User Interface (T1061)' } + ), + id: 'T1061', + name: 'Graphical User Interface', + reference: 'https://attack.mitre.org/techniques/T1061', + tactics: 'execution', + value: 'graphicalUserInterface', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.groupPolicyModificationDescription', + { defaultMessage: 'Group Policy Modification (T1484)' } + ), + id: 'T1484', + name: 'Group Policy Modification', + reference: 'https://attack.mitre.org/techniques/T1484', + tactics: 'defense-evasion', + value: 'groupPolicyModification', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.histcontrolDescription', + { defaultMessage: 'HISTCONTROL (T1148)' } + ), + id: 'T1148', + name: 'HISTCONTROL', + reference: 'https://attack.mitre.org/techniques/T1148', + tactics: 'defense-evasion', + value: 'histcontrol', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.hardwareAdditionsDescription', + { defaultMessage: 'Hardware Additions (T1200)' } + ), + id: 'T1200', + name: 'Hardware Additions', + reference: 'https://attack.mitre.org/techniques/T1200', + tactics: 'initial-access', + value: 'hardwareAdditions', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.hiddenFilesAndDirectoriesDescription', + { defaultMessage: 'Hidden Files and Directories (T1158)' } + ), + id: 'T1158', + name: 'Hidden Files and Directories', + reference: 'https://attack.mitre.org/techniques/T1158', + tactics: 'defense-evasion,persistence', + value: 'hiddenFilesAndDirectories', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.hiddenUsersDescription', + { defaultMessage: 'Hidden Users (T1147)' } + ), + id: 'T1147', + name: 'Hidden Users', + reference: 'https://attack.mitre.org/techniques/T1147', + tactics: 'defense-evasion', + value: 'hiddenUsers', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.hiddenWindowDescription', + { defaultMessage: 'Hidden Window (T1143)' } + ), + id: 'T1143', + name: 'Hidden Window', + reference: 'https://attack.mitre.org/techniques/T1143', + tactics: 'defense-evasion', + value: 'hiddenWindow', + }, + { + label: i18n.translate('xpack.siem.detectionEngine.mitreAttackTechniques.hookingDescription', { + defaultMessage: 'Hooking (T1179)', + }), + id: 'T1179', + name: 'Hooking', + reference: 'https://attack.mitre.org/techniques/T1179', + tactics: 'persistence,privilege-escalation,credential-access', + value: 'hooking', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.hypervisorDescription', + { defaultMessage: 'Hypervisor (T1062)' } + ), + id: 'T1062', + name: 'Hypervisor', + reference: 'https://attack.mitre.org/techniques/T1062', + tactics: 'persistence', + value: 'hypervisor', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.imageFileExecutionOptionsInjectionDescription', + { defaultMessage: 'Image File Execution Options Injection (T1183)' } + ), + id: 'T1183', + name: 'Image File Execution Options Injection', + reference: 'https://attack.mitre.org/techniques/T1183', + tactics: 'privilege-escalation,persistence,defense-evasion', + value: 'imageFileExecutionOptionsInjection', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.implantContainerImageDescription', + { defaultMessage: 'Implant Container Image (T1525)' } + ), + id: 'T1525', + name: 'Implant Container Image', + reference: 'https://attack.mitre.org/techniques/T1525', + tactics: 'persistence', + value: 'implantContainerImage', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.indicatorBlockingDescription', + { defaultMessage: 'Indicator Blocking (T1054)' } + ), + id: 'T1054', + name: 'Indicator Blocking', + reference: 'https://attack.mitre.org/techniques/T1054', + tactics: 'defense-evasion', + value: 'indicatorBlocking', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.indicatorRemovalFromToolsDescription', + { defaultMessage: 'Indicator Removal from Tools (T1066)' } + ), + id: 'T1066', + name: 'Indicator Removal from Tools', + reference: 'https://attack.mitre.org/techniques/T1066', + tactics: 'defense-evasion', + value: 'indicatorRemovalFromTools', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.indicatorRemovalOnHostDescription', + { defaultMessage: 'Indicator Removal on Host (T1070)' } + ), + id: 'T1070', + name: 'Indicator Removal on Host', + reference: 'https://attack.mitre.org/techniques/T1070', + tactics: 'defense-evasion', + value: 'indicatorRemovalOnHost', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.indirectCommandExecutionDescription', + { defaultMessage: 'Indirect Command Execution (T1202)' } + ), + id: 'T1202', + name: 'Indirect Command Execution', + reference: 'https://attack.mitre.org/techniques/T1202', + tactics: 'defense-evasion', + value: 'indirectCommandExecution', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.inhibitSystemRecoveryDescription', + { defaultMessage: 'Inhibit System Recovery (T1490)' } + ), + id: 'T1490', + name: 'Inhibit System Recovery', + reference: 'https://attack.mitre.org/techniques/T1490', + tactics: 'impact', + value: 'inhibitSystemRecovery', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.inputCaptureDescription', + { defaultMessage: 'Input Capture (T1056)' } + ), + id: 'T1056', + name: 'Input Capture', + reference: 'https://attack.mitre.org/techniques/T1056', + tactics: 'collection,credential-access', + value: 'inputCapture', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.inputPromptDescription', + { defaultMessage: 'Input Prompt (T1141)' } + ), + id: 'T1141', + name: 'Input Prompt', + reference: 'https://attack.mitre.org/techniques/T1141', + tactics: 'credential-access', + value: 'inputPrompt', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.installRootCertificateDescription', + { defaultMessage: 'Install Root Certificate (T1130)' } + ), + id: 'T1130', + name: 'Install Root Certificate', + reference: 'https://attack.mitre.org/techniques/T1130', + tactics: 'defense-evasion', + value: 'installRootCertificate', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.installUtilDescription', + { defaultMessage: 'InstallUtil (T1118)' } + ), + id: 'T1118', + name: 'InstallUtil', + reference: 'https://attack.mitre.org/techniques/T1118', + tactics: 'defense-evasion,execution', + value: 'installUtil', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.internalSpearphishingDescription', + { defaultMessage: 'Internal Spearphishing (T1534)' } + ), + id: 'T1534', + name: 'Internal Spearphishing', + reference: 'https://attack.mitre.org/techniques/T1534', + tactics: 'lateral-movement', + value: 'internalSpearphishing', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.kerberoastingDescription', + { defaultMessage: 'Kerberoasting (T1208)' } + ), + id: 'T1208', + name: 'Kerberoasting', + reference: 'https://attack.mitre.org/techniques/T1208', + tactics: 'credential-access', + value: 'kerberoasting', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.kernelModulesAndExtensionsDescription', + { defaultMessage: 'Kernel Modules and Extensions (T1215)' } + ), + id: 'T1215', + name: 'Kernel Modules and Extensions', + reference: 'https://attack.mitre.org/techniques/T1215', + tactics: 'persistence', + value: 'kernelModulesAndExtensions', + }, + { + label: i18n.translate('xpack.siem.detectionEngine.mitreAttackTechniques.keychainDescription', { + defaultMessage: 'Keychain (T1142)', + }), + id: 'T1142', + name: 'Keychain', + reference: 'https://attack.mitre.org/techniques/T1142', + tactics: 'credential-access', + value: 'keychain', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.lcLoadDylibAdditionDescription', + { defaultMessage: 'LC_LOAD_DYLIB Addition (T1161)' } + ), + id: 'T1161', + name: 'LC_LOAD_DYLIB Addition', + reference: 'https://attack.mitre.org/techniques/T1161', + tactics: 'persistence', + value: 'lcLoadDylibAddition', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.lcMainHijackingDescription', + { defaultMessage: 'LC_MAIN Hijacking (T1149)' } + ), + id: 'T1149', + name: 'LC_MAIN Hijacking', + reference: 'https://attack.mitre.org/techniques/T1149', + tactics: 'defense-evasion', + value: 'lcMainHijacking', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.llmnrNbtNsPoisoningAndRelayDescription', + { defaultMessage: 'LLMNR/NBT-NS Poisoning and Relay (T1171)' } + ), + id: 'T1171', + name: 'LLMNR/NBT-NS Poisoning and Relay', + reference: 'https://attack.mitre.org/techniques/T1171', + tactics: 'credential-access', + value: 'llmnrNbtNsPoisoningAndRelay', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.lsassDriverDescription', + { defaultMessage: 'LSASS Driver (T1177)' } + ), + id: 'T1177', + name: 'LSASS Driver', + reference: 'https://attack.mitre.org/techniques/T1177', + tactics: 'execution,persistence', + value: 'lsassDriver', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.launchAgentDescription', + { defaultMessage: 'Launch Agent (T1159)' } + ), + id: 'T1159', + name: 'Launch Agent', + reference: 'https://attack.mitre.org/techniques/T1159', + tactics: 'persistence', + value: 'launchAgent', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.launchDaemonDescription', + { defaultMessage: 'Launch Daemon (T1160)' } + ), + id: 'T1160', + name: 'Launch Daemon', + reference: 'https://attack.mitre.org/techniques/T1160', + tactics: 'persistence,privilege-escalation', + value: 'launchDaemon', + }, + { + label: i18n.translate('xpack.siem.detectionEngine.mitreAttackTechniques.launchctlDescription', { + defaultMessage: 'Launchctl (T1152)', + }), + id: 'T1152', + name: 'Launchctl', + reference: 'https://attack.mitre.org/techniques/T1152', + tactics: 'defense-evasion,execution,persistence', + value: 'launchctl', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.localJobSchedulingDescription', + { defaultMessage: 'Local Job Scheduling (T1168)' } + ), + id: 'T1168', + name: 'Local Job Scheduling', + reference: 'https://attack.mitre.org/techniques/T1168', + tactics: 'persistence,execution', + value: 'localJobScheduling', + }, + { + label: i18n.translate('xpack.siem.detectionEngine.mitreAttackTechniques.loginItemDescription', { + defaultMessage: 'Login Item (T1162)', + }), + id: 'T1162', + name: 'Login Item', + reference: 'https://attack.mitre.org/techniques/T1162', + tactics: 'persistence', + value: 'loginItem', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.logonScriptsDescription', + { defaultMessage: 'Logon Scripts (T1037)' } + ), + id: 'T1037', + name: 'Logon Scripts', + reference: 'https://attack.mitre.org/techniques/T1037', + tactics: 'lateral-movement,persistence', + value: 'logonScripts', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.manInTheBrowserDescription', + { defaultMessage: 'Man in the Browser (T1185)' } + ), + id: 'T1185', + name: 'Man in the Browser', + reference: 'https://attack.mitre.org/techniques/T1185', + tactics: 'collection', + value: 'manInTheBrowser', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.masqueradingDescription', + { defaultMessage: 'Masquerading (T1036)' } + ), + id: 'T1036', + name: 'Masquerading', + reference: 'https://attack.mitre.org/techniques/T1036', + tactics: 'defense-evasion', + value: 'masquerading', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.modifyExistingServiceDescription', + { defaultMessage: 'Modify Existing Service (T1031)' } + ), + id: 'T1031', + name: 'Modify Existing Service', + reference: 'https://attack.mitre.org/techniques/T1031', + tactics: 'persistence', + value: 'modifyExistingService', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.modifyRegistryDescription', + { defaultMessage: 'Modify Registry (T1112)' } + ), + id: 'T1112', + name: 'Modify Registry', + reference: 'https://attack.mitre.org/techniques/T1112', + tactics: 'defense-evasion', + value: 'modifyRegistry', + }, + { + label: i18n.translate('xpack.siem.detectionEngine.mitreAttackTechniques.mshtaDescription', { + defaultMessage: 'Mshta (T1170)', + }), + id: 'T1170', + name: 'Mshta', + reference: 'https://attack.mitre.org/techniques/T1170', + tactics: 'defense-evasion,execution', + value: 'mshta', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.multiStageChannelsDescription', + { defaultMessage: 'Multi-Stage Channels (T1104)' } + ), + id: 'T1104', + name: 'Multi-Stage Channels', + reference: 'https://attack.mitre.org/techniques/T1104', + tactics: 'command-and-control', + value: 'multiStageChannels', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.multiHopProxyDescription', + { defaultMessage: 'Multi-hop Proxy (T1188)' } + ), + id: 'T1188', + name: 'Multi-hop Proxy', + reference: 'https://attack.mitre.org/techniques/T1188', + tactics: 'command-and-control', + value: 'multiHopProxy', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.multibandCommunicationDescription', + { defaultMessage: 'Multiband Communication (T1026)' } + ), + id: 'T1026', + name: 'Multiband Communication', + reference: 'https://attack.mitre.org/techniques/T1026', + tactics: 'command-and-control', + value: 'multibandCommunication', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.multilayerEncryptionDescription', + { defaultMessage: 'Multilayer Encryption (T1079)' } + ), + id: 'T1079', + name: 'Multilayer Encryption', + reference: 'https://attack.mitre.org/techniques/T1079', + tactics: 'command-and-control', + value: 'multilayerEncryption', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.ntfsFileAttributesDescription', + { defaultMessage: 'NTFS File Attributes (T1096)' } + ), + id: 'T1096', + name: 'NTFS File Attributes', + reference: 'https://attack.mitre.org/techniques/T1096', + tactics: 'defense-evasion', + value: 'ntfsFileAttributes', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.netshHelperDllDescription', + { defaultMessage: 'Netsh Helper DLL (T1128)' } + ), + id: 'T1128', + name: 'Netsh Helper DLL', + reference: 'https://attack.mitre.org/techniques/T1128', + tactics: 'persistence', + value: 'netshHelperDll', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.networkDenialOfServiceDescription', + { defaultMessage: 'Network Denial of Service (T1498)' } + ), + id: 'T1498', + name: 'Network Denial of Service', + reference: 'https://attack.mitre.org/techniques/T1498', + tactics: 'impact', + value: 'networkDenialOfService', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.networkServiceScanningDescription', + { defaultMessage: 'Network Service Scanning (T1046)' } + ), + id: 'T1046', + name: 'Network Service Scanning', + reference: 'https://attack.mitre.org/techniques/T1046', + tactics: 'discovery', + value: 'networkServiceScanning', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.networkShareConnectionRemovalDescription', + { defaultMessage: 'Network Share Connection Removal (T1126)' } + ), + id: 'T1126', + name: 'Network Share Connection Removal', + reference: 'https://attack.mitre.org/techniques/T1126', + tactics: 'defense-evasion', + value: 'networkShareConnectionRemoval', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.networkShareDiscoveryDescription', + { defaultMessage: 'Network Share Discovery (T1135)' } + ), + id: 'T1135', + name: 'Network Share Discovery', + reference: 'https://attack.mitre.org/techniques/T1135', + tactics: 'discovery', + value: 'networkShareDiscovery', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.networkSniffingDescription', + { defaultMessage: 'Network Sniffing (T1040)' } + ), + id: 'T1040', + name: 'Network Sniffing', + reference: 'https://attack.mitre.org/techniques/T1040', + tactics: 'credential-access,discovery', + value: 'networkSniffing', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.newServiceDescription', + { defaultMessage: 'New Service (T1050)' } + ), + id: 'T1050', + name: 'New Service', + reference: 'https://attack.mitre.org/techniques/T1050', + tactics: 'persistence,privilege-escalation', + value: 'newService', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.obfuscatedFilesOrInformationDescription', + { defaultMessage: 'Obfuscated Files or Information (T1027)' } + ), + id: 'T1027', + name: 'Obfuscated Files or Information', + reference: 'https://attack.mitre.org/techniques/T1027', + tactics: 'defense-evasion', + value: 'obfuscatedFilesOrInformation', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.officeApplicationStartupDescription', + { defaultMessage: 'Office Application Startup (T1137)' } + ), + id: 'T1137', + name: 'Office Application Startup', + reference: 'https://attack.mitre.org/techniques/T1137', + tactics: 'persistence', + value: 'officeApplicationStartup', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.parentPidSpoofingDescription', + { defaultMessage: 'Parent PID Spoofing (T1502)' } + ), + id: 'T1502', + name: 'Parent PID Spoofing', + reference: 'https://attack.mitre.org/techniques/T1502', + tactics: 'defense-evasion,privilege-escalation', + value: 'parentPidSpoofing', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.passTheHashDescription', + { defaultMessage: 'Pass the Hash (T1075)' } + ), + id: 'T1075', + name: 'Pass the Hash', + reference: 'https://attack.mitre.org/techniques/T1075', + tactics: 'lateral-movement', + value: 'passTheHash', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.passTheTicketDescription', + { defaultMessage: 'Pass the Ticket (T1097)' } + ), + id: 'T1097', + name: 'Pass the Ticket', + reference: 'https://attack.mitre.org/techniques/T1097', + tactics: 'lateral-movement', + value: 'passTheTicket', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.passwordFilterDllDescription', + { defaultMessage: 'Password Filter DLL (T1174)' } + ), + id: 'T1174', + name: 'Password Filter DLL', + reference: 'https://attack.mitre.org/techniques/T1174', + tactics: 'credential-access', + value: 'passwordFilterDll', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.passwordPolicyDiscoveryDescription', + { defaultMessage: 'Password Policy Discovery (T1201)' } + ), + id: 'T1201', + name: 'Password Policy Discovery', + reference: 'https://attack.mitre.org/techniques/T1201', + tactics: 'discovery', + value: 'passwordPolicyDiscovery', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.pathInterceptionDescription', + { defaultMessage: 'Path Interception (T1034)' } + ), + id: 'T1034', + name: 'Path Interception', + reference: 'https://attack.mitre.org/techniques/T1034', + tactics: 'persistence,privilege-escalation', + value: 'pathInterception', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.peripheralDeviceDiscoveryDescription', + { defaultMessage: 'Peripheral Device Discovery (T1120)' } + ), + id: 'T1120', + name: 'Peripheral Device Discovery', + reference: 'https://attack.mitre.org/techniques/T1120', + tactics: 'discovery', + value: 'peripheralDeviceDiscovery', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.permissionGroupsDiscoveryDescription', + { defaultMessage: 'Permission Groups Discovery (T1069)' } + ), + id: 'T1069', + name: 'Permission Groups Discovery', + reference: 'https://attack.mitre.org/techniques/T1069', + tactics: 'discovery', + value: 'permissionGroupsDiscovery', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.plistModificationDescription', + { defaultMessage: 'Plist Modification (T1150)' } + ), + id: 'T1150', + name: 'Plist Modification', + reference: 'https://attack.mitre.org/techniques/T1150', + tactics: 'defense-evasion,persistence,privilege-escalation', + value: 'plistModification', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.portKnockingDescription', + { defaultMessage: 'Port Knocking (T1205)' } + ), + id: 'T1205', + name: 'Port Knocking', + reference: 'https://attack.mitre.org/techniques/T1205', + tactics: 'defense-evasion,persistence,command-and-control', + value: 'portKnocking', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.portMonitorsDescription', + { defaultMessage: 'Port Monitors (T1013)' } + ), + id: 'T1013', + name: 'Port Monitors', + reference: 'https://attack.mitre.org/techniques/T1013', + tactics: 'persistence,privilege-escalation', + value: 'portMonitors', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.powerShellDescription', + { defaultMessage: 'PowerShell (T1086)' } + ), + id: 'T1086', + name: 'PowerShell', + reference: 'https://attack.mitre.org/techniques/T1086', + tactics: 'execution', + value: 'powerShell', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.powerShellProfileDescription', + { defaultMessage: 'PowerShell Profile (T1504)' } + ), + id: 'T1504', + name: 'PowerShell Profile', + reference: 'https://attack.mitre.org/techniques/T1504', + tactics: 'persistence,privilege-escalation', + value: 'powerShellProfile', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.privateKeysDescription', + { defaultMessage: 'Private Keys (T1145)' } + ), + id: 'T1145', + name: 'Private Keys', + reference: 'https://attack.mitre.org/techniques/T1145', + tactics: 'credential-access', + value: 'privateKeys', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.processDiscoveryDescription', + { defaultMessage: 'Process Discovery (T1057)' } + ), + id: 'T1057', + name: 'Process Discovery', + reference: 'https://attack.mitre.org/techniques/T1057', + tactics: 'discovery', + value: 'processDiscovery', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.processDoppelgangingDescription', + { defaultMessage: 'Process Doppelgänging (T1186)' } + ), + id: 'T1186', + name: 'Process Doppelgänging', + reference: 'https://attack.mitre.org/techniques/T1186', + tactics: 'defense-evasion', + value: 'processDoppelganging', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.processHollowingDescription', + { defaultMessage: 'Process Hollowing (T1093)' } + ), + id: 'T1093', + name: 'Process Hollowing', + reference: 'https://attack.mitre.org/techniques/T1093', + tactics: 'defense-evasion', + value: 'processHollowing', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.processInjectionDescription', + { defaultMessage: 'Process Injection (T1055)' } + ), + id: 'T1055', + name: 'Process Injection', + reference: 'https://attack.mitre.org/techniques/T1055', + tactics: 'defense-evasion,privilege-escalation', + value: 'processInjection', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.queryRegistryDescription', + { defaultMessage: 'Query Registry (T1012)' } + ), + id: 'T1012', + name: 'Query Registry', + reference: 'https://attack.mitre.org/techniques/T1012', + tactics: 'discovery', + value: 'queryRegistry', + }, + { + label: i18n.translate('xpack.siem.detectionEngine.mitreAttackTechniques.rcCommonDescription', { + defaultMessage: 'Rc.common (T1163)', + }), + id: 'T1163', + name: 'Rc.common', + reference: 'https://attack.mitre.org/techniques/T1163', + tactics: 'persistence', + value: 'rcCommon', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.reOpenedApplicationsDescription', + { defaultMessage: 'Re-opened Applications (T1164)' } + ), + id: 'T1164', + name: 'Re-opened Applications', + reference: 'https://attack.mitre.org/techniques/T1164', + tactics: 'persistence', + value: 'reOpenedApplications', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.redundantAccessDescription', + { defaultMessage: 'Redundant Access (T1108)' } + ), + id: 'T1108', + name: 'Redundant Access', + reference: 'https://attack.mitre.org/techniques/T1108', + tactics: 'defense-evasion,persistence', + value: 'redundantAccess', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.registryRunKeysStartupFolderDescription', + { defaultMessage: 'Registry Run Keys / Startup Folder (T1060)' } + ), + id: 'T1060', + name: 'Registry Run Keys / Startup Folder', + reference: 'https://attack.mitre.org/techniques/T1060', + tactics: 'persistence', + value: 'registryRunKeysStartupFolder', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.regsvcsRegasmDescription', + { defaultMessage: 'Regsvcs/Regasm (T1121)' } + ), + id: 'T1121', + name: 'Regsvcs/Regasm', + reference: 'https://attack.mitre.org/techniques/T1121', + tactics: 'defense-evasion,execution', + value: 'regsvcsRegasm', + }, + { + label: i18n.translate('xpack.siem.detectionEngine.mitreAttackTechniques.regsvr32Description', { + defaultMessage: 'Regsvr32 (T1117)', + }), + id: 'T1117', + name: 'Regsvr32', + reference: 'https://attack.mitre.org/techniques/T1117', + tactics: 'defense-evasion,execution', + value: 'regsvr32', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.remoteAccessToolsDescription', + { defaultMessage: 'Remote Access Tools (T1219)' } + ), + id: 'T1219', + name: 'Remote Access Tools', + reference: 'https://attack.mitre.org/techniques/T1219', + tactics: 'command-and-control', + value: 'remoteAccessTools', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.remoteDesktopProtocolDescription', + { defaultMessage: 'Remote Desktop Protocol (T1076)' } + ), + id: 'T1076', + name: 'Remote Desktop Protocol', + reference: 'https://attack.mitre.org/techniques/T1076', + tactics: 'lateral-movement', + value: 'remoteDesktopProtocol', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.remoteFileCopyDescription', + { defaultMessage: 'Remote File Copy (T1105)' } + ), + id: 'T1105', + name: 'Remote File Copy', + reference: 'https://attack.mitre.org/techniques/T1105', + tactics: 'command-and-control,lateral-movement', + value: 'remoteFileCopy', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.remoteServicesDescription', + { defaultMessage: 'Remote Services (T1021)' } + ), + id: 'T1021', + name: 'Remote Services', + reference: 'https://attack.mitre.org/techniques/T1021', + tactics: 'lateral-movement', + value: 'remoteServices', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.remoteSystemDiscoveryDescription', + { defaultMessage: 'Remote System Discovery (T1018)' } + ), + id: 'T1018', + name: 'Remote System Discovery', + reference: 'https://attack.mitre.org/techniques/T1018', + tactics: 'discovery', + value: 'remoteSystemDiscovery', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.replicationThroughRemovableMediaDescription', + { defaultMessage: 'Replication Through Removable Media (T1091)' } + ), + id: 'T1091', + name: 'Replication Through Removable Media', + reference: 'https://attack.mitre.org/techniques/T1091', + tactics: 'lateral-movement,initial-access', + value: 'replicationThroughRemovableMedia', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.resourceHijackingDescription', + { defaultMessage: 'Resource Hijacking (T1496)' } + ), + id: 'T1496', + name: 'Resource Hijacking', + reference: 'https://attack.mitre.org/techniques/T1496', + tactics: 'impact', + value: 'resourceHijacking', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.revertCloudInstanceDescription', + { defaultMessage: 'Revert Cloud Instance (T1536)' } + ), + id: 'T1536', + name: 'Revert Cloud Instance', + reference: 'https://attack.mitre.org/techniques/T1536', + tactics: 'defense-evasion', + value: 'revertCloudInstance', + }, + { + label: i18n.translate('xpack.siem.detectionEngine.mitreAttackTechniques.rootkitDescription', { + defaultMessage: 'Rootkit (T1014)', + }), + id: 'T1014', + name: 'Rootkit', + reference: 'https://attack.mitre.org/techniques/T1014', + tactics: 'defense-evasion', + value: 'rootkit', + }, + { + label: i18n.translate('xpack.siem.detectionEngine.mitreAttackTechniques.rundll32Description', { + defaultMessage: 'Rundll32 (T1085)', + }), + id: 'T1085', + name: 'Rundll32', + reference: 'https://attack.mitre.org/techniques/T1085', + tactics: 'defense-evasion,execution', + value: 'rundll32', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.runtimeDataManipulationDescription', + { defaultMessage: 'Runtime Data Manipulation (T1494)' } + ), + id: 'T1494', + name: 'Runtime Data Manipulation', + reference: 'https://attack.mitre.org/techniques/T1494', + tactics: 'impact', + value: 'runtimeDataManipulation', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.sidHistoryInjectionDescription', + { defaultMessage: 'SID-History Injection (T1178)' } + ), + id: 'T1178', + name: 'SID-History Injection', + reference: 'https://attack.mitre.org/techniques/T1178', + tactics: 'privilege-escalation', + value: 'sidHistoryInjection', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.sipAndTrustProviderHijackingDescription', + { defaultMessage: 'SIP and Trust Provider Hijacking (T1198)' } + ), + id: 'T1198', + name: 'SIP and Trust Provider Hijacking', + reference: 'https://attack.mitre.org/techniques/T1198', + tactics: 'defense-evasion,persistence', + value: 'sipAndTrustProviderHijacking', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.sshHijackingDescription', + { defaultMessage: 'SSH Hijacking (T1184)' } + ), + id: 'T1184', + name: 'SSH Hijacking', + reference: 'https://attack.mitre.org/techniques/T1184', + tactics: 'lateral-movement', + value: 'sshHijacking', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.scheduledTaskDescription', + { defaultMessage: 'Scheduled Task (T1053)' } + ), + id: 'T1053', + name: 'Scheduled Task', + reference: 'https://attack.mitre.org/techniques/T1053', + tactics: 'execution,persistence,privilege-escalation', + value: 'scheduledTask', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.scheduledTransferDescription', + { defaultMessage: 'Scheduled Transfer (T1029)' } + ), + id: 'T1029', + name: 'Scheduled Transfer', + reference: 'https://attack.mitre.org/techniques/T1029', + tactics: 'exfiltration', + value: 'scheduledTransfer', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.screenCaptureDescription', + { defaultMessage: 'Screen Capture (T1113)' } + ), + id: 'T1113', + name: 'Screen Capture', + reference: 'https://attack.mitre.org/techniques/T1113', + tactics: 'collection', + value: 'screenCapture', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.screensaverDescription', + { defaultMessage: 'Screensaver (T1180)' } + ), + id: 'T1180', + name: 'Screensaver', + reference: 'https://attack.mitre.org/techniques/T1180', + tactics: 'persistence', + value: 'screensaver', + }, + { + label: i18n.translate('xpack.siem.detectionEngine.mitreAttackTechniques.scriptingDescription', { + defaultMessage: 'Scripting (T1064)', + }), + id: 'T1064', + name: 'Scripting', + reference: 'https://attack.mitre.org/techniques/T1064', + tactics: 'defense-evasion,execution', + value: 'scripting', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.securitySoftwareDiscoveryDescription', + { defaultMessage: 'Security Software Discovery (T1063)' } + ), + id: 'T1063', + name: 'Security Software Discovery', + reference: 'https://attack.mitre.org/techniques/T1063', + tactics: 'discovery', + value: 'securitySoftwareDiscovery', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.securitySupportProviderDescription', + { defaultMessage: 'Security Support Provider (T1101)' } + ), + id: 'T1101', + name: 'Security Support Provider', + reference: 'https://attack.mitre.org/techniques/T1101', + tactics: 'persistence', + value: 'securitySupportProvider', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.securitydMemoryDescription', + { defaultMessage: 'Securityd Memory (T1167)' } + ), + id: 'T1167', + name: 'Securityd Memory', + reference: 'https://attack.mitre.org/techniques/T1167', + tactics: 'credential-access', + value: 'securitydMemory', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.serverSoftwareComponentDescription', + { defaultMessage: 'Server Software Component (T1505)' } + ), + id: 'T1505', + name: 'Server Software Component', + reference: 'https://attack.mitre.org/techniques/T1505', + tactics: 'persistence', + value: 'serverSoftwareComponent', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.serviceExecutionDescription', + { defaultMessage: 'Service Execution (T1035)' } + ), + id: 'T1035', + name: 'Service Execution', + reference: 'https://attack.mitre.org/techniques/T1035', + tactics: 'execution', + value: 'serviceExecution', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.serviceRegistryPermissionsWeaknessDescription', + { defaultMessage: 'Service Registry Permissions Weakness (T1058)' } + ), + id: 'T1058', + name: 'Service Registry Permissions Weakness', + reference: 'https://attack.mitre.org/techniques/T1058', + tactics: 'persistence,privilege-escalation', + value: 'serviceRegistryPermissionsWeakness', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.serviceStopDescription', + { defaultMessage: 'Service Stop (T1489)' } + ), + id: 'T1489', + name: 'Service Stop', + reference: 'https://attack.mitre.org/techniques/T1489', + tactics: 'impact', + value: 'serviceStop', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.setuidAndSetgidDescription', + { defaultMessage: 'Setuid and Setgid (T1166)' } + ), + id: 'T1166', + name: 'Setuid and Setgid', + reference: 'https://attack.mitre.org/techniques/T1166', + tactics: 'privilege-escalation,persistence', + value: 'setuidAndSetgid', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.sharedWebrootDescription', + { defaultMessage: 'Shared Webroot (T1051)' } + ), + id: 'T1051', + name: 'Shared Webroot', + reference: 'https://attack.mitre.org/techniques/T1051', + tactics: 'lateral-movement', + value: 'sharedWebroot', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.shortcutModificationDescription', + { defaultMessage: 'Shortcut Modification (T1023)' } + ), + id: 'T1023', + name: 'Shortcut Modification', + reference: 'https://attack.mitre.org/techniques/T1023', + tactics: 'persistence', + value: 'shortcutModification', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.signedBinaryProxyExecutionDescription', + { defaultMessage: 'Signed Binary Proxy Execution (T1218)' } + ), + id: 'T1218', + name: 'Signed Binary Proxy Execution', + reference: 'https://attack.mitre.org/techniques/T1218', + tactics: 'defense-evasion,execution', + value: 'signedBinaryProxyExecution', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.signedScriptProxyExecutionDescription', + { defaultMessage: 'Signed Script Proxy Execution (T1216)' } + ), + id: 'T1216', + name: 'Signed Script Proxy Execution', + reference: 'https://attack.mitre.org/techniques/T1216', + tactics: 'defense-evasion,execution', + value: 'signedScriptProxyExecution', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.softwareDiscoveryDescription', + { defaultMessage: 'Software Discovery (T1518)' } + ), + id: 'T1518', + name: 'Software Discovery', + reference: 'https://attack.mitre.org/techniques/T1518', + tactics: 'discovery', + value: 'softwareDiscovery', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.softwarePackingDescription', + { defaultMessage: 'Software Packing (T1045)' } + ), + id: 'T1045', + name: 'Software Packing', + reference: 'https://attack.mitre.org/techniques/T1045', + tactics: 'defense-evasion', + value: 'softwarePacking', + }, + { + label: i18n.translate('xpack.siem.detectionEngine.mitreAttackTechniques.sourceDescription', { + defaultMessage: 'Source (T1153)', + }), + id: 'T1153', + name: 'Source', + reference: 'https://attack.mitre.org/techniques/T1153', + tactics: 'execution', + value: 'source', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.spaceAfterFilenameDescription', + { defaultMessage: 'Space after Filename (T1151)' } + ), + id: 'T1151', + name: 'Space after Filename', + reference: 'https://attack.mitre.org/techniques/T1151', + tactics: 'defense-evasion,execution', + value: 'spaceAfterFilename', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.spearphishingAttachmentDescription', + { defaultMessage: 'Spearphishing Attachment (T1193)' } + ), + id: 'T1193', + name: 'Spearphishing Attachment', + reference: 'https://attack.mitre.org/techniques/T1193', + tactics: 'initial-access', + value: 'spearphishingAttachment', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.spearphishingLinkDescription', + { defaultMessage: 'Spearphishing Link (T1192)' } + ), + id: 'T1192', + name: 'Spearphishing Link', + reference: 'https://attack.mitre.org/techniques/T1192', + tactics: 'initial-access', + value: 'spearphishingLink', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.spearphishingViaServiceDescription', + { defaultMessage: 'Spearphishing via Service (T1194)' } + ), + id: 'T1194', + name: 'Spearphishing via Service', + reference: 'https://attack.mitre.org/techniques/T1194', + tactics: 'initial-access', + value: 'spearphishingViaService', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.standardApplicationLayerProtocolDescription', + { defaultMessage: 'Standard Application Layer Protocol (T1071)' } + ), + id: 'T1071', + name: 'Standard Application Layer Protocol', + reference: 'https://attack.mitre.org/techniques/T1071', + tactics: 'command-and-control', + value: 'standardApplicationLayerProtocol', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.standardCryptographicProtocolDescription', + { defaultMessage: 'Standard Cryptographic Protocol (T1032)' } + ), + id: 'T1032', + name: 'Standard Cryptographic Protocol', + reference: 'https://attack.mitre.org/techniques/T1032', + tactics: 'command-and-control', + value: 'standardCryptographicProtocol', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.standardNonApplicationLayerProtocolDescription', + { defaultMessage: 'Standard Non-Application Layer Protocol (T1095)' } + ), + id: 'T1095', + name: 'Standard Non-Application Layer Protocol', + reference: 'https://attack.mitre.org/techniques/T1095', + tactics: 'command-and-control', + value: 'standardNonApplicationLayerProtocol', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.startupItemsDescription', + { defaultMessage: 'Startup Items (T1165)' } + ), + id: 'T1165', + name: 'Startup Items', + reference: 'https://attack.mitre.org/techniques/T1165', + tactics: 'persistence,privilege-escalation', + value: 'startupItems', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.stealApplicationAccessTokenDescription', + { defaultMessage: 'Steal Application Access Token (T1528)' } + ), + id: 'T1528', + name: 'Steal Application Access Token', + reference: 'https://attack.mitre.org/techniques/T1528', + tactics: 'credential-access', + value: 'stealApplicationAccessToken', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.stealWebSessionCookieDescription', + { defaultMessage: 'Steal Web Session Cookie (T1539)' } + ), + id: 'T1539', + name: 'Steal Web Session Cookie', + reference: 'https://attack.mitre.org/techniques/T1539', + tactics: 'credential-access', + value: 'stealWebSessionCookie', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.storedDataManipulationDescription', + { defaultMessage: 'Stored Data Manipulation (T1492)' } + ), + id: 'T1492', + name: 'Stored Data Manipulation', + reference: 'https://attack.mitre.org/techniques/T1492', + tactics: 'impact', + value: 'storedDataManipulation', + }, + { + label: i18n.translate('xpack.siem.detectionEngine.mitreAttackTechniques.sudoDescription', { + defaultMessage: 'Sudo (T1169)', + }), + id: 'T1169', + name: 'Sudo', + reference: 'https://attack.mitre.org/techniques/T1169', + tactics: 'privilege-escalation', + value: 'sudo', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.sudoCachingDescription', + { defaultMessage: 'Sudo Caching (T1206)' } + ), + id: 'T1206', + name: 'Sudo Caching', + reference: 'https://attack.mitre.org/techniques/T1206', + tactics: 'privilege-escalation', + value: 'sudoCaching', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.supplyChainCompromiseDescription', + { defaultMessage: 'Supply Chain Compromise (T1195)' } + ), + id: 'T1195', + name: 'Supply Chain Compromise', + reference: 'https://attack.mitre.org/techniques/T1195', + tactics: 'initial-access', + value: 'supplyChainCompromise', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.systemFirmwareDescription', + { defaultMessage: 'System Firmware (T1019)' } + ), + id: 'T1019', + name: 'System Firmware', + reference: 'https://attack.mitre.org/techniques/T1019', + tactics: 'persistence', + value: 'systemFirmware', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.systemInformationDiscoveryDescription', + { defaultMessage: 'System Information Discovery (T1082)' } + ), + id: 'T1082', + name: 'System Information Discovery', + reference: 'https://attack.mitre.org/techniques/T1082', + tactics: 'discovery', + value: 'systemInformationDiscovery', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.systemNetworkConfigurationDiscoveryDescription', + { defaultMessage: 'System Network Configuration Discovery (T1016)' } + ), + id: 'T1016', + name: 'System Network Configuration Discovery', + reference: 'https://attack.mitre.org/techniques/T1016', + tactics: 'discovery', + value: 'systemNetworkConfigurationDiscovery', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.systemNetworkConnectionsDiscoveryDescription', + { defaultMessage: 'System Network Connections Discovery (T1049)' } + ), + id: 'T1049', + name: 'System Network Connections Discovery', + reference: 'https://attack.mitre.org/techniques/T1049', + tactics: 'discovery', + value: 'systemNetworkConnectionsDiscovery', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.systemOwnerUserDiscoveryDescription', + { defaultMessage: 'System Owner/User Discovery (T1033)' } + ), + id: 'T1033', + name: 'System Owner/User Discovery', + reference: 'https://attack.mitre.org/techniques/T1033', + tactics: 'discovery', + value: 'systemOwnerUserDiscovery', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.systemServiceDiscoveryDescription', + { defaultMessage: 'System Service Discovery (T1007)' } + ), + id: 'T1007', + name: 'System Service Discovery', + reference: 'https://attack.mitre.org/techniques/T1007', + tactics: 'discovery', + value: 'systemServiceDiscovery', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.systemShutdownRebootDescription', + { defaultMessage: 'System Shutdown/Reboot (T1529)' } + ), + id: 'T1529', + name: 'System Shutdown/Reboot', + reference: 'https://attack.mitre.org/techniques/T1529', + tactics: 'impact', + value: 'systemShutdownReboot', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.systemTimeDiscoveryDescription', + { defaultMessage: 'System Time Discovery (T1124)' } + ), + id: 'T1124', + name: 'System Time Discovery', + reference: 'https://attack.mitre.org/techniques/T1124', + tactics: 'discovery', + value: 'systemTimeDiscovery', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.systemdServiceDescription', + { defaultMessage: 'Systemd Service (T1501)' } + ), + id: 'T1501', + name: 'Systemd Service', + reference: 'https://attack.mitre.org/techniques/T1501', + tactics: 'persistence', + value: 'systemdService', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.taintSharedContentDescription', + { defaultMessage: 'Taint Shared Content (T1080)' } + ), + id: 'T1080', + name: 'Taint Shared Content', + reference: 'https://attack.mitre.org/techniques/T1080', + tactics: 'lateral-movement', + value: 'taintSharedContent', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.templateInjectionDescription', + { defaultMessage: 'Template Injection (T1221)' } + ), + id: 'T1221', + name: 'Template Injection', + reference: 'https://attack.mitre.org/techniques/T1221', + tactics: 'defense-evasion', + value: 'templateInjection', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.thirdPartySoftwareDescription', + { defaultMessage: 'Third-party Software (T1072)' } + ), + id: 'T1072', + name: 'Third-party Software', + reference: 'https://attack.mitre.org/techniques/T1072', + tactics: 'execution,lateral-movement', + value: 'thirdPartySoftware', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.timeProvidersDescription', + { defaultMessage: 'Time Providers (T1209)' } + ), + id: 'T1209', + name: 'Time Providers', + reference: 'https://attack.mitre.org/techniques/T1209', + tactics: 'persistence', + value: 'timeProviders', + }, + { + label: i18n.translate('xpack.siem.detectionEngine.mitreAttackTechniques.timestompDescription', { + defaultMessage: 'Timestomp (T1099)', + }), + id: 'T1099', + name: 'Timestomp', + reference: 'https://attack.mitre.org/techniques/T1099', + tactics: 'defense-evasion', + value: 'timestomp', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.transferDataToCloudAccountDescription', + { defaultMessage: 'Transfer Data to Cloud Account (T1537)' } + ), + id: 'T1537', + name: 'Transfer Data to Cloud Account', + reference: 'https://attack.mitre.org/techniques/T1537', + tactics: 'exfiltration', + value: 'transferDataToCloudAccount', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.transmittedDataManipulationDescription', + { defaultMessage: 'Transmitted Data Manipulation (T1493)' } + ), + id: 'T1493', + name: 'Transmitted Data Manipulation', + reference: 'https://attack.mitre.org/techniques/T1493', + tactics: 'impact', + value: 'transmittedDataManipulation', + }, + { + label: i18n.translate('xpack.siem.detectionEngine.mitreAttackTechniques.trapDescription', { + defaultMessage: 'Trap (T1154)', + }), + id: 'T1154', + name: 'Trap', + reference: 'https://attack.mitre.org/techniques/T1154', + tactics: 'execution,persistence', + value: 'trap', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.trustedDeveloperUtilitiesDescription', + { defaultMessage: 'Trusted Developer Utilities (T1127)' } + ), + id: 'T1127', + name: 'Trusted Developer Utilities', + reference: 'https://attack.mitre.org/techniques/T1127', + tactics: 'defense-evasion,execution', + value: 'trustedDeveloperUtilities', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.trustedRelationshipDescription', + { defaultMessage: 'Trusted Relationship (T1199)' } + ), + id: 'T1199', + name: 'Trusted Relationship', + reference: 'https://attack.mitre.org/techniques/T1199', + tactics: 'initial-access', + value: 'trustedRelationship', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.twoFactorAuthenticationInterceptionDescription', + { defaultMessage: 'Two-Factor Authentication Interception (T1111)' } + ), + id: 'T1111', + name: 'Two-Factor Authentication Interception', + reference: 'https://attack.mitre.org/techniques/T1111', + tactics: 'credential-access', + value: 'twoFactorAuthenticationInterception', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.uncommonlyUsedPortDescription', + { defaultMessage: 'Uncommonly Used Port (T1065)' } + ), + id: 'T1065', + name: 'Uncommonly Used Port', + reference: 'https://attack.mitre.org/techniques/T1065', + tactics: 'command-and-control', + value: 'uncommonlyUsedPort', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.unusedUnsupportedCloudRegionsDescription', + { defaultMessage: 'Unused/Unsupported Cloud Regions (T1535)' } + ), + id: 'T1535', + name: 'Unused/Unsupported Cloud Regions', + reference: 'https://attack.mitre.org/techniques/T1535', + tactics: 'defense-evasion', + value: 'unusedUnsupportedCloudRegions', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.userExecutionDescription', + { defaultMessage: 'User Execution (T1204)' } + ), + id: 'T1204', + name: 'User Execution', + reference: 'https://attack.mitre.org/techniques/T1204', + tactics: 'execution', + value: 'userExecution', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.validAccountsDescription', + { defaultMessage: 'Valid Accounts (T1078)' } + ), + id: 'T1078', + name: 'Valid Accounts', + reference: 'https://attack.mitre.org/techniques/T1078', + tactics: 'defense-evasion,persistence,privilege-escalation,initial-access', + value: 'validAccounts', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.videoCaptureDescription', + { defaultMessage: 'Video Capture (T1125)' } + ), + id: 'T1125', + name: 'Video Capture', + reference: 'https://attack.mitre.org/techniques/T1125', + tactics: 'collection', + value: 'videoCapture', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.virtualizationSandboxEvasionDescription', + { defaultMessage: 'Virtualization/Sandbox Evasion (T1497)' } + ), + id: 'T1497', + name: 'Virtualization/Sandbox Evasion', + reference: 'https://attack.mitre.org/techniques/T1497', + tactics: 'defense-evasion,discovery', + value: 'virtualizationSandboxEvasion', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.webServiceDescription', + { defaultMessage: 'Web Service (T1102)' } + ), + id: 'T1102', + name: 'Web Service', + reference: 'https://attack.mitre.org/techniques/T1102', + tactics: 'command-and-control,defense-evasion', + value: 'webService', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.webSessionCookieDescription', + { defaultMessage: 'Web Session Cookie (T1506)' } + ), + id: 'T1506', + name: 'Web Session Cookie', + reference: 'https://attack.mitre.org/techniques/T1506', + tactics: 'defense-evasion,lateral-movement', + value: 'webSessionCookie', + }, + { + label: i18n.translate('xpack.siem.detectionEngine.mitreAttackTechniques.webShellDescription', { + defaultMessage: 'Web Shell (T1100)', + }), + id: 'T1100', + name: 'Web Shell', + reference: 'https://attack.mitre.org/techniques/T1100', + tactics: 'persistence,privilege-escalation', + value: 'webShell', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.windowsAdminSharesDescription', + { defaultMessage: 'Windows Admin Shares (T1077)' } + ), + id: 'T1077', + name: 'Windows Admin Shares', + reference: 'https://attack.mitre.org/techniques/T1077', + tactics: 'lateral-movement', + value: 'windowsAdminShares', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.windowsManagementInstrumentationDescription', + { defaultMessage: 'Windows Management Instrumentation (T1047)' } + ), + id: 'T1047', + name: 'Windows Management Instrumentation', + reference: 'https://attack.mitre.org/techniques/T1047', + tactics: 'execution', + value: 'windowsManagementInstrumentation', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.windowsManagementInstrumentationEventSubscriptionDescription', + { defaultMessage: 'Windows Management Instrumentation Event Subscription (T1084)' } + ), + id: 'T1084', + name: 'Windows Management Instrumentation Event Subscription', + reference: 'https://attack.mitre.org/techniques/T1084', + tactics: 'persistence', + value: 'windowsManagementInstrumentationEventSubscription', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.windowsRemoteManagementDescription', + { defaultMessage: 'Windows Remote Management (T1028)' } + ), + id: 'T1028', + name: 'Windows Remote Management', + reference: 'https://attack.mitre.org/techniques/T1028', + tactics: 'execution,lateral-movement', + value: 'windowsRemoteManagement', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.winlogonHelperDllDescription', + { defaultMessage: 'Winlogon Helper DLL (T1004)' } + ), + id: 'T1004', + name: 'Winlogon Helper DLL', + reference: 'https://attack.mitre.org/techniques/T1004', + tactics: 'persistence', + value: 'winlogonHelperDll', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.xslScriptProcessingDescription', + { defaultMessage: 'XSL Script Processing (T1220)' } + ), + id: 'T1220', + name: 'XSL Script Processing', + reference: 'https://attack.mitre.org/techniques/T1220', + tactics: 'defense-evasion,execution', + value: 'xslScriptProcessing', + }, +]; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/mitre/types.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/mitre/types.ts new file mode 100644 index 000000000000..a1e7a2e66ab8 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/mitre/types.ts @@ -0,0 +1,21 @@ +/* + * 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 interface MitreOptions { + id: string; + name: string; + reference: string; + value: string; +} + +export interface MitreTacticsOptions extends MitreOptions { + text: string; +} + +export interface MitreTechniquesOptions extends MitreOptions { + label: string; + tactics: string; +} diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/signals/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/signals/index.tsx index ca178db9cd97..74b7b9349c2c 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/signals/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/signals/index.tsx @@ -64,7 +64,7 @@ export const SignalsTable = React.memo(() => { {({ to, from, setQuery, deleteQuery, isInitializing }) => ( tactics.map(t => `{ + id: '${t.id}', + name: '${t.name}', + reference: '${t.reference}', + text: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTactics.${camelCase(t.name)}Description', { + defaultMessage: '${t.name} (${t.id})' + }), + value: '${camelCase(t.name)}' +}`.replace(/(\r\n|\n|\r)/gm, ' ')); + +const getTechniquesOptions = techniques => techniques.map(t => `{ + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.${camelCase(t.name)}Description', { + defaultMessage: '${t.name} (${t.id})' + }), + id: '${t.id}', + name: '${t.name}', + reference: '${t.reference}', + tactics: '${t.tactics.join()}', + value: '${camelCase(t.name)}' +}`.replace(/(\r\n|\n|\r)/gm, ' ')); + +const getIdReference = references => references.reduce((obj, extRef) => { + if (extRef.source_name === 'mitre-attack') { + return { + id: extRef.external_id, reference: extRef.url + }; + } + return obj; +}, { id: '', reference: '' }); + +async function main() { + fetch(MITRE_ENTREPRISE_ATTACK_URL) + .then(res => res.json()) + .then(json => { + const mitreData = json.objects; + const tactics = mitreData.filter(obj => obj.type === 'x-mitre-tactic').reduce((acc, item) => { + const { id, reference } = getIdReference(item.external_references); + + return [...acc, { + name: item.name, + id, + reference, + }]; + }, []); + const techniques = mitreData.filter(obj => obj.type === 'attack-pattern').reduce((acc, item) => { + let tactics = []; + const { id, reference } = getIdReference(item.external_references); + if (item.kill_chain_phases != null && item.kill_chain_phases.length > 0) { + item.kill_chain_phases.forEach(tactic => { + tactics = [...tactics, tactic.phase_name]; + }); + } + + return [...acc, { + name: item.name, + id, + reference, + tactics, + }]; + }, []); + + const body = + `/* + * 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 { i18n } from '@kbn/i18n'; + + import { MitreTacticsOptions, MitreTechniquesOptions } from './types'; + + export const tactics = ${JSON.stringify(tactics, null, 2)}; + + export const tacticsOptions: MitreTacticsOptions[] = + ${JSON.stringify(getTacticsOptions(tactics), null, 2).replace(/}"/g, '}').replace(/"{/g, '{')}; + + export const techniques = ${JSON.stringify(techniques, null, 2)}; + + export const techniquesOptions: MitreTechniquesOptions[] = + ${JSON.stringify(getTechniquesOptions(techniques), null, 2).replace(/}"/g, '}').replace(/"{/g, '{')}; + `; + + fs.writeFileSync(`${OUTPUT_DIRECTORY}/mitre_tactics_techniques.ts`, body, 'utf-8'); + + }); +} + +if (require.main === module) { + main(); +} diff --git a/x-pack/legacy/plugins/siem/server/kibana.index.ts b/x-pack/legacy/plugins/siem/server/kibana.index.ts index bb0958b32fa1..f56e6b3c3f55 100644 --- a/x-pack/legacy/plugins/siem/server/kibana.index.ts +++ b/x-pack/legacy/plugins/siem/server/kibana.index.ts @@ -6,18 +6,18 @@ import { PluginInitializerContext } from 'src/core/server'; -import { rulesAlertType } from './lib/detection_engine/alerts/rules_alert_type'; -import { isAlertExecutor } from './lib/detection_engine/alerts/types'; -import { createRulesRoute } from './lib/detection_engine/routes/create_rules_route'; +import { signalRulesAlertType } from './lib/detection_engine/signals/signal_rule_alert_type'; +import { createRulesRoute } from './lib/detection_engine/routes/rules/create_rules_route'; import { createIndexRoute } from './lib/detection_engine/routes/index/create_index_route'; import { readIndexRoute } from './lib/detection_engine/routes/index/read_index_route'; -import { readRulesRoute } from './lib/detection_engine/routes/read_rules_route'; -import { findRulesRoute } from './lib/detection_engine/routes/find_rules_route'; -import { deleteRulesRoute } from './lib/detection_engine/routes/delete_rules_route'; -import { updateRulesRoute } from './lib/detection_engine/routes/update_rules_route'; +import { readRulesRoute } from './lib/detection_engine/routes/rules/read_rules_route'; +import { findRulesRoute } from './lib/detection_engine/routes/rules/find_rules_route'; +import { deleteRulesRoute } from './lib/detection_engine/routes/rules/delete_rules_route'; +import { updateRulesRoute } from './lib/detection_engine/routes/rules/update_rules_route'; import { setSignalsStatusRoute } from './lib/detection_engine/routes/signals/open_close_signals_route'; import { ServerFacade } from './types'; import { deleteIndexRoute } from './lib/detection_engine/routes/index/delete_index_route'; +import { isAlertExecutor } from './lib/detection_engine/signals/types'; const APP_ID = 'siem'; @@ -26,7 +26,7 @@ export const initServerWithKibana = (context: PluginInitializerContext, __legacy const version = context.env.packageInfo.version; if (__legacy.plugins.alerting != null) { - const type = rulesAlertType({ logger, version }); + const type = signalRulesAlertType({ logger, version }); if (isAlertExecutor(type)) { __legacy.plugins.alerting.setup.registerType(type); } diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/types.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/types.ts deleted file mode 100644 index c9d265ebffac..000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/types.ts +++ /dev/null @@ -1,264 +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 { get } from 'lodash/fp'; - -import { SIGNALS_ID } from '../../../../common/constants'; -import { - Alert, - AlertType, - State, - AlertExecutorOptions, -} from '../../../../../alerting/server/types'; -import { AlertsClient } from '../../../../../alerting/server/alerts_client'; -import { ActionsClient } from '../../../../../actions/server/actions_client'; -import { RequestFacade } from '../../../types'; -import { SearchResponse } from '../../types'; -import { esFilters } from '../../../../../../../../src/plugins/data/server'; - -export type PartialFilter = Partial; - -export interface IMitreAttack { - id: string; - name: string; - reference: string; -} -export interface ThreatParams { - framework: string; - tactic: IMitreAttack; - techniques: IMitreAttack[]; -} -export interface RuleAlertParams { - description: string; - enabled: boolean; - falsePositives: string[]; - filters: PartialFilter[] | undefined | null; - from: string; - immutable: boolean; - index: string[]; - interval: string; - ruleId: string | undefined | null; - language: string | undefined | null; - maxSignals: number; - riskScore: number; - outputIndex: string; - name: string; - query: string | undefined | null; - references: string[]; - savedId: string | undefined | null; - meta: Record | undefined | null; - severity: string; - tags: string[]; - to: string; - threats: ThreatParams[] | undefined | null; - type: 'query' | 'saved_query'; -} - -export type RuleAlertParamsRest = Omit< - RuleAlertParams, - 'ruleId' | 'falsePositives' | 'maxSignals' | 'savedId' | 'riskScore' | 'outputIndex' -> & { - rule_id: RuleAlertParams['ruleId']; - false_positives: RuleAlertParams['falsePositives']; - saved_id: RuleAlertParams['savedId']; - max_signals: RuleAlertParams['maxSignals']; - risk_score: RuleAlertParams['riskScore']; - output_index: RuleAlertParams['outputIndex']; -}; - -export interface SignalsParams { - signalIds: string[] | undefined | null; - query: object | undefined | null; - status: 'open' | 'closed'; -} - -export type SignalsRestParams = Omit & { - signal_ids: SignalsParams['signalIds']; -}; - -export type OutputRuleAlertRest = RuleAlertParamsRest & { - id: string; - created_by: string | undefined | null; - updated_by: string | undefined | null; -}; - -export type UpdateRuleAlertParamsRest = Partial & { - id: string | undefined; - rule_id: RuleAlertParams['ruleId'] | undefined; -}; - -export interface FindParamsRest { - per_page: number; - page: number; - sort_field: string; - sort_order: 'asc' | 'desc'; - fields: string[]; - filter: string; -} - -export interface Clients { - alertsClient: AlertsClient; - actionsClient: ActionsClient; -} - -export type RuleParams = RuleAlertParams & Clients; - -export type UpdateRuleParams = Partial & { - id: string | undefined | null; -} & Clients; - -export type DeleteRuleParams = Clients & { - id: string | undefined; - ruleId: string | undefined | null; -}; - -export interface FindRulesRequest extends Omit { - query: { - per_page: number; - page: number; - search?: string; - sort_field?: string; - filter?: string; - fields?: string[]; - sort_order?: 'asc' | 'desc'; - }; -} - -export interface FindRuleParams { - alertsClient: AlertsClient; - perPage?: number; - page?: number; - sortField?: string; - filter?: string; - fields?: string[]; - sortOrder?: 'asc' | 'desc'; -} - -export interface ReadRuleParams { - alertsClient: AlertsClient; - id?: string | undefined | null; - ruleId?: string | undefined | null; -} - -export interface ReadRuleByRuleId { - alertsClient: AlertsClient; - ruleId: string; -} - -export type RuleTypeParams = Omit; - -export type RuleAlertType = Alert & { - id: string; - params: RuleTypeParams; -}; - -export interface RulesRequest extends RequestFacade { - payload: RuleAlertParamsRest; -} - -export interface SignalsRequest extends RequestFacade { - payload: SignalsRestParams; -} - -export interface UpdateRulesRequest extends RequestFacade { - payload: UpdateRuleAlertParamsRest; -} - -export type RuleExecutorOptions = Omit & { - params: RuleAlertParams & { - scrollSize: number; - scrollLock: string; - }; -}; - -export type SearchTypes = - | string - | string[] - | number - | number[] - | boolean - | boolean[] - | object - | object[]; - -export interface SignalSource { - [key: string]: SearchTypes; - '@timestamp': string; -} - -export interface BulkResponse { - took: number; - errors: boolean; - items: [ - { - create: { - _index: string; - _type?: string; - _id: string; - _version: number; - result?: string; - _shards?: { - total: number; - successful: number; - failed: number; - }; - _seq_no?: number; - _primary_term?: number; - status: number; - error?: { - type: string; - reason: string; - index_uuid?: string; - shard: string; - index: string; - }; - }; - } - ]; -} - -export interface MGetResponse { - docs: GetResponse[]; -} -export interface GetResponse { - _index: string; - _type: string; - _id: string; - _version: number; - _seq_no: number; - _primary_term: number; - found: boolean; - _source: SearchTypes; -} - -export type SignalSearchResponse = SearchResponse; -export type SignalSourceHit = SignalSearchResponse['hits']['hits'][0]; - -export type QueryRequest = Omit & { - query: { id: string | undefined; rule_id: string | undefined }; -}; - -// This returns true because by default a RuleAlertTypeDefinition is an AlertType -// since we are only increasing the strictness of params. -export const isAlertExecutor = (obj: RuleAlertTypeDefinition): obj is AlertType => { - return true; -}; - -export type RuleAlertTypeDefinition = Omit & { - executor: ({ services, params, state }: RuleExecutorOptions) => Promise; -}; - -export const isAlertTypes = (obj: unknown[]): obj is RuleAlertType[] => { - return obj.every(rule => isAlertType(rule)); -}; - -export const isAlertType = (obj: unknown): obj is RuleAlertType => { - return get('alertTypeId', obj) === SIGNALS_ID; -}; - -export const isAlertTypeArray = (objArray: unknown[]): objArray is RuleAlertType[] => { - return objArray.length === 0 || isAlertType(objArray[0]); -}; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.test.ts deleted file mode 100644 index 41052ab4bbb1..000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.test.ts +++ /dev/null @@ -1,1107 +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 uuid from 'uuid'; -import { savedObjectsClientMock } from 'src/core/server/mocks'; - -import { Logger } from '../../../../../../../../src/core/server'; -import { - buildBulkBody, - generateId, - singleBulkCreate, - singleSearchAfter, - searchAfterAndBulkCreate, - buildEventTypeSignal, - buildSignal, - buildRule, -} from './utils'; -import { - sampleDocNoSortId, - sampleRuleAlertParams, - sampleDocSearchResultsNoSortId, - sampleDocSearchResultsNoSortIdNoHits, - sampleDocSearchResultsNoSortIdNoVersion, - sampleDocSearchResultsWithSortId, - sampleEmptyDocSearchResults, - repeatedSearchResultsWithSortId, - sampleBulkCreateDuplicateResult, - sampleRuleGuid, - sampleRule, - sampleIdGuid, -} from './__mocks__/es_results'; -import { DEFAULT_SIGNALS_INDEX } from '../../../../common/constants'; -import { OutputRuleAlertRest } from './types'; -import { Signal } from '../../types'; - -const mockLogger: Logger = { - log: jest.fn(), - trace: jest.fn(), - debug: jest.fn(), - info: jest.fn(), - warn: jest.fn(), - error: jest.fn(), - fatal: jest.fn(), -}; - -const mockService = { - callCluster: jest.fn(), - alertInstanceFactory: jest.fn(), - savedObjectsClient: savedObjectsClientMock.create(), -}; - -describe('utils', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - describe('buildBulkBody', () => { - test('if bulk body builds well-defined body', () => { - const sampleParams = sampleRuleAlertParams(); - const fakeSignalSourceHit = buildBulkBody({ - doc: sampleDocNoSortId(), - ruleParams: sampleParams, - id: sampleRuleGuid, - name: 'rule-name', - createdBy: 'elastic', - updatedBy: 'elastic', - interval: '5m', - enabled: true, - tags: ['some fake tag 1', 'some fake tag 2'], - }); - // Timestamp will potentially always be different so remove it for the test - delete fakeSignalSourceHit['@timestamp']; - expect(fakeSignalSourceHit).toEqual({ - someKey: 'someValue', - event: { - kind: 'signal', - }, - signal: { - parent: { - id: sampleIdGuid, - type: 'event', - index: 'myFakeSignalIndex', - depth: 1, - }, - original_time: 'someTimeStamp', - status: 'open', - rule: { - id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', - rule_id: 'rule-1', - false_positives: [], - max_signals: 10000, - risk_score: 50, - output_index: '.siem-signals', - description: 'Detecting root and admin users', - from: 'now-6m', - immutable: false, - index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], - interval: '5m', - language: 'kuery', - name: 'rule-name', - query: 'user.name: root or user.name: admin', - references: ['http://google.com'], - severity: 'high', - tags: ['some fake tag 1', 'some fake tag 2'], - type: 'query', - to: 'now', - enabled: true, - created_by: 'elastic', - updated_by: 'elastic', - }, - }, - }); - }); - - test('if bulk body builds original_event if it exists on the event to begin with', () => { - const sampleParams = sampleRuleAlertParams(); - const doc = sampleDocNoSortId(); - doc._source.event = { - action: 'socket_opened', - module: 'system', - dataset: 'socket', - kind: 'event', - }; - const fakeSignalSourceHit = buildBulkBody({ - doc, - ruleParams: sampleParams, - id: sampleRuleGuid, - name: 'rule-name', - createdBy: 'elastic', - updatedBy: 'elastic', - interval: '5m', - enabled: true, - tags: ['some fake tag 1', 'some fake tag 2'], - }); - // Timestamp will potentially always be different so remove it for the test - delete fakeSignalSourceHit['@timestamp']; - expect(fakeSignalSourceHit).toEqual({ - someKey: 'someValue', - event: { - action: 'socket_opened', - dataset: 'socket', - kind: 'signal', - module: 'system', - }, - signal: { - original_event: { - action: 'socket_opened', - dataset: 'socket', - kind: 'event', - module: 'system', - }, - parent: { - id: sampleIdGuid, - type: 'event', - index: 'myFakeSignalIndex', - depth: 1, - }, - original_time: 'someTimeStamp', - status: 'open', - rule: { - id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', - rule_id: 'rule-1', - false_positives: [], - max_signals: 10000, - risk_score: 50, - output_index: '.siem-signals', - description: 'Detecting root and admin users', - from: 'now-6m', - immutable: false, - index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], - interval: '5m', - language: 'kuery', - name: 'rule-name', - query: 'user.name: root or user.name: admin', - references: ['http://google.com'], - severity: 'high', - tags: ['some fake tag 1', 'some fake tag 2'], - type: 'query', - to: 'now', - enabled: true, - created_by: 'elastic', - updated_by: 'elastic', - }, - }, - }); - }); - - test('if bulk body builds original_event if it exists on the event to begin with but no kind information', () => { - const sampleParams = sampleRuleAlertParams(); - const doc = sampleDocNoSortId(); - doc._source.event = { - action: 'socket_opened', - module: 'system', - dataset: 'socket', - }; - const fakeSignalSourceHit = buildBulkBody({ - doc, - ruleParams: sampleParams, - id: sampleRuleGuid, - name: 'rule-name', - createdBy: 'elastic', - updatedBy: 'elastic', - interval: '5m', - enabled: true, - tags: ['some fake tag 1', 'some fake tag 2'], - }); - // Timestamp will potentially always be different so remove it for the test - delete fakeSignalSourceHit['@timestamp']; - expect(fakeSignalSourceHit).toEqual({ - someKey: 'someValue', - event: { - action: 'socket_opened', - dataset: 'socket', - kind: 'signal', - module: 'system', - }, - signal: { - original_event: { - action: 'socket_opened', - dataset: 'socket', - module: 'system', - }, - parent: { - id: sampleIdGuid, - type: 'event', - index: 'myFakeSignalIndex', - depth: 1, - }, - original_time: 'someTimeStamp', - status: 'open', - rule: { - id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', - rule_id: 'rule-1', - false_positives: [], - max_signals: 10000, - risk_score: 50, - output_index: '.siem-signals', - description: 'Detecting root and admin users', - from: 'now-6m', - immutable: false, - index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], - interval: '5m', - language: 'kuery', - name: 'rule-name', - query: 'user.name: root or user.name: admin', - references: ['http://google.com'], - severity: 'high', - tags: ['some fake tag 1', 'some fake tag 2'], - type: 'query', - to: 'now', - enabled: true, - created_by: 'elastic', - updated_by: 'elastic', - }, - }, - }); - }); - - test('if bulk body builds original_event if it exists on the event to begin with with only kind information', () => { - const sampleParams = sampleRuleAlertParams(); - const doc = sampleDocNoSortId(); - doc._source.event = { - kind: 'event', - }; - const fakeSignalSourceHit = buildBulkBody({ - doc, - ruleParams: sampleParams, - id: sampleRuleGuid, - name: 'rule-name', - createdBy: 'elastic', - updatedBy: 'elastic', - interval: '5m', - enabled: true, - tags: ['some fake tag 1', 'some fake tag 2'], - }); - // Timestamp will potentially always be different so remove it for the test - delete fakeSignalSourceHit['@timestamp']; - expect(fakeSignalSourceHit).toEqual({ - someKey: 'someValue', - event: { - kind: 'signal', - }, - signal: { - original_event: { - kind: 'event', - }, - parent: { - id: sampleIdGuid, - type: 'event', - index: 'myFakeSignalIndex', - depth: 1, - }, - original_time: 'someTimeStamp', - status: 'open', - rule: { - id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', - rule_id: 'rule-1', - false_positives: [], - max_signals: 10000, - risk_score: 50, - output_index: '.siem-signals', - description: 'Detecting root and admin users', - from: 'now-6m', - immutable: false, - index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], - interval: '5m', - language: 'kuery', - name: 'rule-name', - query: 'user.name: root or user.name: admin', - references: ['http://google.com'], - severity: 'high', - tags: ['some fake tag 1', 'some fake tag 2'], - type: 'query', - to: 'now', - enabled: true, - created_by: 'elastic', - updated_by: 'elastic', - }, - }, - }); - }); - }); - describe('singleBulkCreate', () => { - describe('create signal id gereateId', () => { - test('two docs with same index, id, and version should have same id', () => { - const findex = 'myfakeindex'; - const fid = 'somefakeid'; - const version = '1'; - const ruleId = 'rule-1'; - // 'myfakeindexsomefakeid1rule-1' - const generatedHash = '342404d620be4344d6d90dd0461d1d1848aec457944d5c5f40cc0cbfedb36679'; - const firstHash = generateId(findex, fid, version, ruleId); - const secondHash = generateId(findex, fid, version, ruleId); - expect(firstHash).toEqual(generatedHash); - expect(secondHash).toEqual(generatedHash); - expect(Buffer.byteLength(firstHash, 'utf8')).toBeLessThan(512); // 512 bytes is maximum size of _id field - expect(Buffer.byteLength(secondHash, 'utf8')).toBeLessThan(512); - }); - test('two docs with different index, id, and version should have different id', () => { - const findex = 'myfakeindex'; - const findex2 = 'mysecondfakeindex'; - const fid = 'somefakeid'; - const version = '1'; - const ruleId = 'rule-1'; - // 'myfakeindexsomefakeid1rule-1' - const firstGeneratedHash = - '342404d620be4344d6d90dd0461d1d1848aec457944d5c5f40cc0cbfedb36679'; - // 'mysecondfakeindexsomefakeid1rule-1' - const secondGeneratedHash = - 'a852941273f805ffe9006e574601acc8ae1148d6c0b3f7f8c4785cba8f6b768a'; - const firstHash = generateId(findex, fid, version, ruleId); - const secondHash = generateId(findex2, fid, version, ruleId); - expect(firstHash).toEqual(firstGeneratedHash); - expect(secondHash).toEqual(secondGeneratedHash); - expect(Buffer.byteLength(firstHash, 'utf8')).toBeLessThan(512); // 512 bytes is maximum size of _id field - expect(Buffer.byteLength(secondHash, 'utf8')).toBeLessThan(512); - expect(firstHash).not.toEqual(secondHash); - }); - test('two docs with same index, different id, and same version should have different id', () => { - const findex = 'myfakeindex'; - const fid = 'somefakeid'; - const fid2 = 'somefakeid2'; - const version = '1'; - const ruleId = 'rule-1'; - // 'myfakeindexsomefakeid1rule-1' - const firstGeneratedHash = - '342404d620be4344d6d90dd0461d1d1848aec457944d5c5f40cc0cbfedb36679'; - // 'myfakeindexsomefakeid21rule-1' - const secondGeneratedHash = - '7d33faea18159fd010c4b79890620e8b12cdc88ec1d370149d0e5552ce860255'; - const firstHash = generateId(findex, fid, version, ruleId); - const secondHash = generateId(findex, fid2, version, ruleId); - expect(firstHash).toEqual(firstGeneratedHash); - expect(secondHash).toEqual(secondGeneratedHash); - expect(Buffer.byteLength(firstHash, 'utf8')).toBeLessThan(512); // 512 bytes is maximum size of _id field - expect(Buffer.byteLength(secondHash, 'utf8')).toBeLessThan(512); - expect(firstHash).not.toEqual(secondHash); - }); - test('two docs with same index, same id, and different version should have different id', () => { - const findex = 'myfakeindex'; - const fid = 'somefakeid'; - const version = '1'; - const version2 = '2'; - const ruleId = 'rule-1'; - // 'myfakeindexsomefakeid1rule-1' - const firstGeneratedHash = - '342404d620be4344d6d90dd0461d1d1848aec457944d5c5f40cc0cbfedb36679'; - // myfakeindexsomefakeid2rule-1' - const secondGeneratedHash = - 'f016f3071fa9df9221d2fb2ba92389d4d388a4347c6ec7a4012c01cb1c640a40'; - const firstHash = generateId(findex, fid, version, ruleId); - const secondHash = generateId(findex, fid, version2, ruleId); - expect(firstHash).toEqual(firstGeneratedHash); - expect(secondHash).toEqual(secondGeneratedHash); - expect(Buffer.byteLength(firstHash, 'utf8')).toBeLessThan(512); // 512 bytes is maximum size of _id field - expect(Buffer.byteLength(secondHash, 'utf8')).toBeLessThan(512); - expect(firstHash).not.toEqual(secondHash); - }); - test('Ensure generated id is less than 512 bytes, even for really really long strings', () => { - const longIndexName = - 'myfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindex'; - const fid = 'somefakeid'; - const version = '1'; - const ruleId = 'rule-1'; - const firstHash = generateId(longIndexName, fid, version, ruleId); - expect(Buffer.byteLength(firstHash, 'utf8')).toBeLessThan(512); // 512 bytes is maximum size of _id field - }); - test('two docs with same index, same id, same version number, and different rule ids should have different id', () => { - const findex = 'myfakeindex'; - const fid = 'somefakeid'; - const version = '1'; - const ruleId = 'rule-1'; - const ruleId2 = 'rule-2'; - // 'myfakeindexsomefakeid1rule-1' - const firstGeneratedHash = - '342404d620be4344d6d90dd0461d1d1848aec457944d5c5f40cc0cbfedb36679'; - // myfakeindexsomefakeid1rule-2' - const secondGeneratedHash = - '1eb04f997086f8b3b143d4d9b18ac178c4a7423f71a5dad9ba8b9e92603c6863'; - const firstHash = generateId(findex, fid, version, ruleId); - const secondHash = generateId(findex, fid, version, ruleId2); - expect(firstHash).toEqual(firstGeneratedHash); - expect(secondHash).toEqual(secondGeneratedHash); - expect(Buffer.byteLength(firstHash, 'utf8')).toBeLessThan(512); // 512 bytes is maximum size of _id field - expect(Buffer.byteLength(secondHash, 'utf8')).toBeLessThan(512); - expect(firstHash).not.toEqual(secondHash); - }); - }); - test('create successful bulk create', async () => { - const sampleParams = sampleRuleAlertParams(); - const sampleSearchResult = sampleDocSearchResultsNoSortId; - mockService.callCluster.mockReturnValueOnce({ - took: 100, - errors: false, - items: [ - { - fakeItemValue: 'fakeItemKey', - }, - ], - }); - const successfulsingleBulkCreate = await singleBulkCreate({ - someResult: sampleSearchResult(), - ruleParams: sampleParams, - services: mockService, - logger: mockLogger, - id: sampleRuleGuid, - signalsIndex: DEFAULT_SIGNALS_INDEX, - name: 'rule-name', - createdBy: 'elastic', - updatedBy: 'elastic', - interval: '5m', - enabled: true, - tags: ['some fake tag 1', 'some fake tag 2'], - }); - expect(successfulsingleBulkCreate).toEqual(true); - }); - test('create successful bulk create with docs with no versioning', async () => { - const sampleParams = sampleRuleAlertParams(); - const sampleSearchResult = sampleDocSearchResultsNoSortIdNoVersion; - mockService.callCluster.mockReturnValueOnce({ - took: 100, - errors: false, - items: [ - { - fakeItemValue: 'fakeItemKey', - }, - ], - }); - const successfulsingleBulkCreate = await singleBulkCreate({ - someResult: sampleSearchResult(), - ruleParams: sampleParams, - services: mockService, - logger: mockLogger, - id: sampleRuleGuid, - signalsIndex: DEFAULT_SIGNALS_INDEX, - name: 'rule-name', - createdBy: 'elastic', - updatedBy: 'elastic', - interval: '5m', - enabled: true, - tags: ['some fake tag 1', 'some fake tag 2'], - }); - expect(successfulsingleBulkCreate).toEqual(true); - }); - test('create unsuccessful bulk create due to empty search results', async () => { - const sampleParams = sampleRuleAlertParams(); - const sampleSearchResult = sampleEmptyDocSearchResults; - mockService.callCluster.mockReturnValue(false); - const successfulsingleBulkCreate = await singleBulkCreate({ - someResult: sampleSearchResult, - ruleParams: sampleParams, - services: mockService, - logger: mockLogger, - id: sampleRuleGuid, - signalsIndex: DEFAULT_SIGNALS_INDEX, - name: 'rule-name', - createdBy: 'elastic', - updatedBy: 'elastic', - interval: '5m', - enabled: true, - tags: ['some fake tag 1', 'some fake tag 2'], - }); - expect(successfulsingleBulkCreate).toEqual(true); - }); - test('create successful bulk create when bulk create has errors', async () => { - const sampleParams = sampleRuleAlertParams(); - const sampleSearchResult = sampleDocSearchResultsNoSortId; - mockService.callCluster.mockReturnValue(sampleBulkCreateDuplicateResult); - const successfulsingleBulkCreate = await singleBulkCreate({ - someResult: sampleSearchResult(), - ruleParams: sampleParams, - services: mockService, - logger: mockLogger, - id: sampleRuleGuid, - signalsIndex: DEFAULT_SIGNALS_INDEX, - name: 'rule-name', - createdBy: 'elastic', - updatedBy: 'elastic', - interval: '5m', - enabled: true, - tags: ['some fake tag 1', 'some fake tag 2'], - }); - expect(mockLogger.error).toHaveBeenCalled(); - expect(successfulsingleBulkCreate).toEqual(true); - }); - }); - describe('singleSearchAfter', () => { - test('if singleSearchAfter works without a given sort id', async () => { - let searchAfterSortId; - const sampleParams = sampleRuleAlertParams(); - mockService.callCluster.mockReturnValue(sampleDocSearchResultsNoSortId); - await expect( - singleSearchAfter({ - searchAfterSortId, - ruleParams: sampleParams, - services: mockService, - logger: mockLogger, - pageSize: 1, - filter: undefined, - }) - ).rejects.toThrow('Attempted to search after with empty sort id'); - }); - test('if singleSearchAfter works with a given sort id', async () => { - const searchAfterSortId = '1234567891111'; - const sampleParams = sampleRuleAlertParams(); - mockService.callCluster.mockReturnValue(sampleDocSearchResultsWithSortId); - const searchAfterResult = await singleSearchAfter({ - searchAfterSortId, - ruleParams: sampleParams, - services: mockService, - logger: mockLogger, - pageSize: 1, - filter: undefined, - }); - expect(searchAfterResult).toEqual(sampleDocSearchResultsWithSortId); - }); - test('if singleSearchAfter throws error', async () => { - const searchAfterSortId = '1234567891111'; - const sampleParams = sampleRuleAlertParams(); - mockService.callCluster.mockImplementation(async () => { - throw Error('Fake Error'); - }); - await expect( - singleSearchAfter({ - searchAfterSortId, - ruleParams: sampleParams, - services: mockService, - logger: mockLogger, - pageSize: 1, - filter: undefined, - }) - ).rejects.toThrow('Fake Error'); - }); - }); - describe('searchAfterAndBulkCreate', () => { - test('if successful with empty search results', async () => { - const sampleParams = sampleRuleAlertParams(); - const result = await searchAfterAndBulkCreate({ - someResult: sampleEmptyDocSearchResults, - ruleParams: sampleParams, - services: mockService, - logger: mockLogger, - id: sampleRuleGuid, - signalsIndex: DEFAULT_SIGNALS_INDEX, - name: 'rule-name', - createdBy: 'elastic', - updatedBy: 'elastic', - interval: '5m', - enabled: true, - pageSize: 1, - filter: undefined, - tags: ['some fake tag 1', 'some fake tag 2'], - }); - expect(mockService.callCluster).toHaveBeenCalledTimes(0); - expect(result).toEqual(true); - }); - test('if successful iteration of while loop with maxDocs', async () => { - const sampleParams = sampleRuleAlertParams(30); - const someGuids = Array.from({ length: 13 }).map(x => uuid.v4()); - mockService.callCluster - .mockReturnValueOnce({ - took: 100, - errors: false, - items: [ - { - fakeItemValue: 'fakeItemKey', - }, - ], - }) - .mockReturnValueOnce(repeatedSearchResultsWithSortId(3, 1, someGuids.slice(0, 3))) - .mockReturnValueOnce({ - took: 100, - errors: false, - items: [ - { - fakeItemValue: 'fakeItemKey', - }, - ], - }) - .mockReturnValueOnce(repeatedSearchResultsWithSortId(3, 1, someGuids.slice(3, 6))) - .mockReturnValueOnce({ - took: 100, - errors: false, - items: [ - { - fakeItemValue: 'fakeItemKey', - }, - ], - }); - const result = await searchAfterAndBulkCreate({ - someResult: repeatedSearchResultsWithSortId(3, 1, someGuids.slice(6, 9)), - ruleParams: sampleParams, - services: mockService, - logger: mockLogger, - id: sampleRuleGuid, - signalsIndex: DEFAULT_SIGNALS_INDEX, - name: 'rule-name', - createdBy: 'elastic', - updatedBy: 'elastic', - interval: '5m', - enabled: true, - pageSize: 1, - filter: undefined, - tags: ['some fake tag 1', 'some fake tag 2'], - }); - expect(mockService.callCluster).toHaveBeenCalledTimes(5); - expect(result).toEqual(true); - }); - test('if unsuccessful first bulk create', async () => { - const someGuids = Array.from({ length: 4 }).map(x => uuid.v4()); - const sampleParams = sampleRuleAlertParams(10); - mockService.callCluster.mockReturnValue(sampleBulkCreateDuplicateResult); - const result = await searchAfterAndBulkCreate({ - someResult: repeatedSearchResultsWithSortId(4, 1, someGuids), - ruleParams: sampleParams, - services: mockService, - logger: mockLogger, - id: sampleRuleGuid, - signalsIndex: DEFAULT_SIGNALS_INDEX, - name: 'rule-name', - createdBy: 'elastic', - updatedBy: 'elastic', - interval: '5m', - enabled: true, - pageSize: 1, - filter: undefined, - tags: ['some fake tag 1', 'some fake tag 2'], - }); - expect(mockLogger.error).toHaveBeenCalled(); - expect(result).toEqual(false); - }); - test('if unsuccessful iteration of searchAfterAndBulkCreate due to empty sort ids', async () => { - const sampleParams = sampleRuleAlertParams(); - mockService.callCluster.mockReturnValueOnce({ - took: 100, - errors: false, - items: [ - { - fakeItemValue: 'fakeItemKey', - }, - ], - }); - const result = await searchAfterAndBulkCreate({ - someResult: sampleDocSearchResultsNoSortId(), - ruleParams: sampleParams, - services: mockService, - logger: mockLogger, - id: sampleRuleGuid, - signalsIndex: DEFAULT_SIGNALS_INDEX, - name: 'rule-name', - createdBy: 'elastic', - updatedBy: 'elastic', - interval: '5m', - enabled: true, - pageSize: 1, - filter: undefined, - tags: ['some fake tag 1', 'some fake tag 2'], - }); - expect(mockLogger.error).toHaveBeenCalled(); - expect(result).toEqual(false); - }); - test('if unsuccessful iteration of searchAfterAndBulkCreate due to empty sort ids and 0 total hits', async () => { - const sampleParams = sampleRuleAlertParams(); - mockService.callCluster.mockReturnValueOnce({ - took: 100, - errors: false, - items: [ - { - fakeItemValue: 'fakeItemKey', - }, - ], - }); - const result = await searchAfterAndBulkCreate({ - someResult: sampleDocSearchResultsNoSortIdNoHits(), - ruleParams: sampleParams, - services: mockService, - logger: mockLogger, - id: sampleRuleGuid, - signalsIndex: DEFAULT_SIGNALS_INDEX, - name: 'rule-name', - createdBy: 'elastic', - updatedBy: 'elastic', - interval: '5m', - enabled: true, - pageSize: 1, - filter: undefined, - tags: ['some fake tag 1', 'some fake tag 2'], - }); - expect(result).toEqual(true); - }); - test('if successful iteration of while loop with maxDocs and search after returns results with no sort ids', async () => { - const sampleParams = sampleRuleAlertParams(10); - const someGuids = Array.from({ length: 4 }).map(x => uuid.v4()); - mockService.callCluster - .mockReturnValueOnce({ - took: 100, - errors: false, - items: [ - { - fakeItemValue: 'fakeItemKey', - }, - ], - }) - .mockReturnValueOnce(sampleDocSearchResultsNoSortId()); - const result = await searchAfterAndBulkCreate({ - someResult: repeatedSearchResultsWithSortId(4, 1, someGuids), - ruleParams: sampleParams, - services: mockService, - logger: mockLogger, - id: sampleRuleGuid, - signalsIndex: DEFAULT_SIGNALS_INDEX, - name: 'rule-name', - createdBy: 'elastic', - updatedBy: 'elastic', - interval: '5m', - enabled: true, - pageSize: 1, - filter: undefined, - tags: ['some fake tag 1', 'some fake tag 2'], - }); - expect(result).toEqual(true); - }); - test('if successful iteration of while loop with maxDocs and search after returns empty results with no sort ids', async () => { - const sampleParams = sampleRuleAlertParams(10); - const someGuids = Array.from({ length: 4 }).map(x => uuid.v4()); - mockService.callCluster - .mockReturnValueOnce({ - took: 100, - errors: false, - items: [ - { - fakeItemValue: 'fakeItemKey', - }, - ], - }) - .mockReturnValueOnce(sampleEmptyDocSearchResults); - const result = await searchAfterAndBulkCreate({ - someResult: repeatedSearchResultsWithSortId(4, 1, someGuids), - ruleParams: sampleParams, - services: mockService, - logger: mockLogger, - id: sampleRuleGuid, - signalsIndex: DEFAULT_SIGNALS_INDEX, - name: 'rule-name', - createdBy: 'elastic', - updatedBy: 'elastic', - interval: '5m', - enabled: true, - pageSize: 1, - filter: undefined, - tags: ['some fake tag 1', 'some fake tag 2'], - }); - expect(result).toEqual(true); - }); - test('if returns false when singleSearchAfter throws an exception', async () => { - const sampleParams = sampleRuleAlertParams(10); - const someGuids = Array.from({ length: 4 }).map(x => uuid.v4()); - mockService.callCluster - .mockReturnValueOnce({ - took: 100, - errors: false, - items: [ - { - fakeItemValue: 'fakeItemKey', - }, - ], - }) - .mockImplementation(() => { - throw Error('Fake Error'); - }); - const result = await searchAfterAndBulkCreate({ - someResult: repeatedSearchResultsWithSortId(4, 1, someGuids), - ruleParams: sampleParams, - services: mockService, - logger: mockLogger, - id: sampleRuleGuid, - signalsIndex: DEFAULT_SIGNALS_INDEX, - name: 'rule-name', - createdBy: 'elastic', - updatedBy: 'elastic', - interval: '5m', - enabled: true, - pageSize: 1, - filter: undefined, - tags: ['some fake tag 1', 'some fake tag 2'], - }); - expect(result).toEqual(false); - }); - }); - - describe('buildEventTypeSignal', () => { - test('it returns the event appended of kind signal if it does not exist', () => { - const doc = sampleDocNoSortId(); - delete doc._source.event; - const eventType = buildEventTypeSignal(doc); - const expected: object = { kind: 'signal' }; - expect(eventType).toEqual(expected); - }); - - test('it returns the event appended of kind signal if it is an empty object', () => { - const doc = sampleDocNoSortId(); - doc._source.event = {}; - const eventType = buildEventTypeSignal(doc); - const expected: object = { kind: 'signal' }; - expect(eventType).toEqual(expected); - }); - - test('it returns the event with kind signal and other properties if they exist', () => { - const doc = sampleDocNoSortId(); - doc._source.event = { - action: 'socket_opened', - module: 'system', - dataset: 'socket', - }; - const eventType = buildEventTypeSignal(doc); - const expected: object = { - action: 'socket_opened', - module: 'system', - dataset: 'socket', - kind: 'signal', - }; - expect(eventType).toEqual(expected); - }); - }); - - describe('buildSignal', () => { - test('it builds a signal as expected without original_event if event does not exist', () => { - const doc = sampleDocNoSortId('d5e8eb51-a6a0-456d-8a15-4b79bfec3d71'); - delete doc._source.event; - const rule: Partial = sampleRule(); - const signal = buildSignal(doc, rule); - const expected: Signal = { - parent: { - id: 'd5e8eb51-a6a0-456d-8a15-4b79bfec3d71', - type: 'event', - index: 'myFakeSignalIndex', - depth: 1, - }, - original_time: 'someTimeStamp', - status: 'open', - rule: { - created_by: 'elastic', - description: 'Detecting root and admin users', - enabled: true, - false_positives: [], - from: 'now-6m', - id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', - immutable: false, - index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], - interval: '5m', - risk_score: 50, - rule_id: 'rule-1', - language: 'kuery', - max_signals: 100, - name: 'Detect Root/Admin Users', - output_index: '.siem-signals', - query: 'user.name: root or user.name: admin', - references: ['http://www.example.com', 'https://ww.example.com'], - severity: 'high', - updated_by: 'elastic', - tags: ['some fake tag 1', 'some fake tag 2'], - to: 'now', - type: 'query', - }, - }; - expect(signal).toEqual(expected); - }); - - test('it builds a signal as expected with original_event if is present', () => { - const doc = sampleDocNoSortId('d5e8eb51-a6a0-456d-8a15-4b79bfec3d71'); - doc._source.event = { - action: 'socket_opened', - dataset: 'socket', - kind: 'event', - module: 'system', - }; - const rule: Partial = sampleRule(); - const signal = buildSignal(doc, rule); - const expected: Signal = { - parent: { - id: 'd5e8eb51-a6a0-456d-8a15-4b79bfec3d71', - type: 'event', - index: 'myFakeSignalIndex', - depth: 1, - }, - original_time: 'someTimeStamp', - original_event: { - action: 'socket_opened', - dataset: 'socket', - kind: 'event', - module: 'system', - }, - status: 'open', - rule: { - created_by: 'elastic', - description: 'Detecting root and admin users', - enabled: true, - false_positives: [], - from: 'now-6m', - id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', - immutable: false, - index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], - interval: '5m', - risk_score: 50, - rule_id: 'rule-1', - language: 'kuery', - max_signals: 100, - name: 'Detect Root/Admin Users', - output_index: '.siem-signals', - query: 'user.name: root or user.name: admin', - references: ['http://www.example.com', 'https://ww.example.com'], - severity: 'high', - updated_by: 'elastic', - tags: ['some fake tag 1', 'some fake tag 2'], - to: 'now', - type: 'query', - }, - }; - expect(signal).toEqual(expected); - }); - }); - - describe('buildRule', () => { - test('it builds a rule as expected with filters present', () => { - const ruleParams = sampleRuleAlertParams(); - ruleParams.filters = [ - { - query: 'host.name: Rebecca', - }, - { - query: 'host.name: Evan', - }, - { - query: 'host.name: Braden', - }, - ]; - const rule = buildRule({ - ruleParams, - name: 'some-name', - id: sampleRuleGuid, - enabled: false, - createdBy: 'elastic', - updatedBy: 'elastic', - interval: 'some interval', - tags: ['some fake tag 1', 'some fake tag 2'], - }); - const expected: Partial = { - created_by: 'elastic', - description: 'Detecting root and admin users', - enabled: false, - false_positives: [], - from: 'now-6m', - id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', - immutable: false, - index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], - interval: 'some interval', - language: 'kuery', - max_signals: 10000, - name: 'some-name', - output_index: '.siem-signals', - query: 'user.name: root or user.name: admin', - references: ['http://google.com'], - risk_score: 50, - rule_id: 'rule-1', - severity: 'high', - tags: ['some fake tag 1', 'some fake tag 2'], - to: 'now', - type: 'query', - updated_by: 'elastic', - filters: [ - { - query: 'host.name: Rebecca', - }, - { - query: 'host.name: Evan', - }, - { - query: 'host.name: Braden', - }, - ], - }; - expect(rule).toEqual(expected); - }); - - test('it omits a null value such as if enabled is null if is present', () => { - const ruleParams = sampleRuleAlertParams(); - ruleParams.filters = undefined; - const rule = buildRule({ - ruleParams, - name: 'some-name', - id: sampleRuleGuid, - enabled: true, - createdBy: 'elastic', - updatedBy: 'elastic', - interval: 'some interval', - tags: ['some fake tag 1', 'some fake tag 2'], - }); - const expected: Partial = { - created_by: 'elastic', - description: 'Detecting root and admin users', - enabled: true, - false_positives: [], - from: 'now-6m', - id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', - immutable: false, - index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], - interval: 'some interval', - language: 'kuery', - max_signals: 10000, - name: 'some-name', - output_index: '.siem-signals', - query: 'user.name: root or user.name: admin', - references: ['http://google.com'], - risk_score: 50, - rule_id: 'rule-1', - severity: 'high', - tags: ['some fake tag 1', 'some fake tag 2'], - to: 'now', - type: 'query', - updated_by: 'elastic', - }; - expect(rule).toEqual(expected); - }); - - test('it omits a null value such as if filters is undefined if is present', () => { - const ruleParams = sampleRuleAlertParams(); - ruleParams.filters = undefined; - const rule = buildRule({ - ruleParams, - name: 'some-name', - id: sampleRuleGuid, - enabled: true, - createdBy: 'elastic', - updatedBy: 'elastic', - interval: 'some interval', - tags: ['some fake tag 1', 'some fake tag 2'], - }); - const expected: Partial = { - created_by: 'elastic', - description: 'Detecting root and admin users', - enabled: true, - false_positives: [], - from: 'now-6m', - id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', - immutable: false, - index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], - interval: 'some interval', - language: 'kuery', - max_signals: 10000, - name: 'some-name', - output_index: '.siem-signals', - query: 'user.name: root or user.name: admin', - references: ['http://google.com'], - risk_score: 50, - rule_id: 'rule-1', - severity: 'high', - tags: ['some fake tag 1', 'some fake tag 2'], - to: 'now', - type: 'query', - updated_by: 'elastic', - }; - expect(rule).toEqual(expected); - }); - }); -}); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.ts deleted file mode 100644 index 1787aa3a3081..000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.ts +++ /dev/null @@ -1,408 +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 { createHash } from 'crypto'; -import { performance } from 'perf_hooks'; -import { pickBy } from 'lodash/fp'; -import { SignalHit, Signal } from '../../types'; -import { Logger } from '../../../../../../../../src/core/server'; -import { AlertServices } from '../../../../../alerting/server/types'; -import { - SignalSourceHit, - SignalSearchResponse, - BulkResponse, - RuleTypeParams, - OutputRuleAlertRest, -} from './types'; -import { buildEventsSearchQuery } from './build_events_query'; - -interface BuildRuleParams { - ruleParams: RuleTypeParams; - name: string; - id: string; - enabled: boolean; - createdBy: string; - updatedBy: string; - interval: string; - tags: string[]; -} - -export const buildRule = ({ - ruleParams, - name, - id, - enabled, - createdBy, - updatedBy, - interval, - tags, -}: BuildRuleParams): Partial => { - return pickBy((value: unknown) => value != null, { - id, - rule_id: ruleParams.ruleId, - false_positives: ruleParams.falsePositives, - saved_id: ruleParams.savedId, - meta: ruleParams.meta, - max_signals: ruleParams.maxSignals, - risk_score: ruleParams.riskScore, - output_index: ruleParams.outputIndex, - description: ruleParams.description, - from: ruleParams.from, - immutable: ruleParams.immutable, - index: ruleParams.index, - interval, - language: ruleParams.language, - name, - query: ruleParams.query, - references: ruleParams.references, - severity: ruleParams.severity, - tags, - type: ruleParams.type, - to: ruleParams.to, - enabled, - filters: ruleParams.filters, - created_by: createdBy, - updated_by: updatedBy, - threats: ruleParams.threats, - }); -}; - -export const buildSignal = (doc: SignalSourceHit, rule: Partial): Signal => { - const signal: Signal = { - parent: { - id: doc._id, - type: 'event', - index: doc._index, - depth: 1, - }, - original_time: doc._source['@timestamp'], - status: 'open', - rule, - }; - if (doc._source.event != null) { - return { ...signal, original_event: doc._source.event }; - } - return signal; -}; - -interface BuildBulkBodyParams { - doc: SignalSourceHit; - ruleParams: RuleTypeParams; - id: string; - name: string; - createdBy: string; - updatedBy: string; - interval: string; - enabled: boolean; - tags: string[]; -} - -export const buildEventTypeSignal = (doc: SignalSourceHit): object => { - if (doc._source.event != null && doc._source.event instanceof Object) { - return { ...doc._source.event, kind: 'signal' }; - } else { - return { kind: 'signal' }; - } -}; - -// format search_after result for signals index. -export const buildBulkBody = ({ - doc, - ruleParams, - id, - name, - createdBy, - updatedBy, - interval, - enabled, - tags, -}: BuildBulkBodyParams): SignalHit => { - const rule = buildRule({ - ruleParams, - id, - name, - enabled, - createdBy, - updatedBy, - interval, - tags, - }); - const signal = buildSignal(doc, rule); - const event = buildEventTypeSignal(doc); - const signalHit: SignalHit = { - ...doc._source, - '@timestamp': new Date().toISOString(), - event, - signal, - }; - return signalHit; -}; - -interface SingleBulkCreateParams { - someResult: SignalSearchResponse; - ruleParams: RuleTypeParams; - services: AlertServices; - logger: Logger; - id: string; - signalsIndex: string; - name: string; - createdBy: string; - updatedBy: string; - interval: string; - enabled: boolean; - tags: string[]; -} - -export const generateId = ( - docIndex: string, - docId: string, - version: string, - ruleId: string -): string => - createHash('sha256') - .update(docIndex.concat(docId, version, ruleId)) - .digest('hex'); - -// Bulk Index documents. -export const singleBulkCreate = async ({ - someResult, - ruleParams, - services, - logger, - id, - signalsIndex, - name, - createdBy, - updatedBy, - interval, - enabled, - tags, -}: SingleBulkCreateParams): Promise => { - if (someResult.hits.hits.length === 0) { - return true; - } - // index documents after creating an ID based on the - // source documents' originating index, and the original - // document _id. This will allow two documents from two - // different indexes with the same ID to be - // indexed, and prevents us from creating any updates - // to the documents once inserted into the signals index, - // while preventing duplicates from being added to the - // signals index if rules are re-run over the same time - // span. Also allow for versioning. - const bulkBody = someResult.hits.hits.flatMap(doc => [ - { - create: { - _index: signalsIndex, - _id: generateId( - doc._index, - doc._id, - doc._version ? doc._version.toString() : '', - ruleParams.ruleId ?? '' - ), - }, - }, - buildBulkBody({ doc, ruleParams, id, name, createdBy, updatedBy, interval, enabled, tags }), - ]); - const time1 = performance.now(); - const firstResult: BulkResponse = await services.callCluster('bulk', { - index: signalsIndex, - refresh: false, - body: bulkBody, - }); - const time2 = performance.now(); - logger.debug( - `individual bulk process time took: ${Number(time2 - time1).toFixed(2)} milliseconds` - ); - logger.debug(`took property says bulk took: ${firstResult.took} milliseconds`); - if (firstResult.errors) { - // go through the response status errors and see what - // types of errors they are, count them up, and log them. - const errorCountMap = firstResult.items.reduce((acc: { [key: string]: number }, item) => { - if (item.create.error) { - const responseStatusKey = item.create.status.toString(); - acc[responseStatusKey] = acc[responseStatusKey] ? acc[responseStatusKey] + 1 : 1; - } - return acc; - }, {}); - /* - the logging output below should look like - {'409': 55} - which is read as "there were 55 counts of 409 errors returned from bulk create" - */ - logger.error( - `[-] bulkResponse had errors with response statuses:counts of...\n${JSON.stringify( - errorCountMap, - null, - 2 - )}` - ); - } - return true; -}; - -interface SingleSearchAfterParams { - searchAfterSortId: string | undefined; - ruleParams: RuleTypeParams; - services: AlertServices; - logger: Logger; - pageSize: number; - filter: unknown; -} - -// utilize search_after for paging results into bulk. -export const singleSearchAfter = async ({ - searchAfterSortId, - ruleParams, - services, - filter, - logger, - pageSize, -}: SingleSearchAfterParams): Promise => { - if (searchAfterSortId == null) { - throw Error('Attempted to search after with empty sort id'); - } - try { - const searchAfterQuery = buildEventsSearchQuery({ - index: ruleParams.index, - from: ruleParams.from, - to: ruleParams.to, - filter, - size: pageSize, - searchAfterSortId, - }); - const nextSearchAfterResult: SignalSearchResponse = await services.callCluster( - 'search', - searchAfterQuery - ); - return nextSearchAfterResult; - } catch (exc) { - logger.error(`[-] nextSearchAfter threw an error ${exc}`); - throw exc; - } -}; - -interface SearchAfterAndBulkCreateParams { - someResult: SignalSearchResponse; - ruleParams: RuleTypeParams; - services: AlertServices; - logger: Logger; - id: string; - signalsIndex: string; - name: string; - createdBy: string; - updatedBy: string; - interval: string; - enabled: boolean; - pageSize: number; - filter: unknown; - tags: string[]; -} - -// search_after through documents and re-index using bulk endpoint. -export const searchAfterAndBulkCreate = async ({ - someResult, - ruleParams, - services, - logger, - id, - signalsIndex, - filter, - name, - createdBy, - updatedBy, - interval, - enabled, - pageSize, - tags, -}: SearchAfterAndBulkCreateParams): Promise => { - if (someResult.hits.hits.length === 0) { - return true; - } - - logger.debug('[+] starting bulk insertion'); - await singleBulkCreate({ - someResult, - ruleParams, - services, - logger, - id, - signalsIndex, - name, - createdBy, - updatedBy, - interval, - enabled, - tags, - }); - const totalHits = - typeof someResult.hits.total === 'number' ? someResult.hits.total : someResult.hits.total.value; - // maxTotalHitsSize represents the total number of docs to - // query for, no matter the size of each individual page of search results. - // If the total number of hits for the overall search result is greater than - // maxSignals, default to requesting a total of maxSignals, otherwise use the - // totalHits in the response from the searchAfter query. - const maxTotalHitsSize = totalHits >= ruleParams.maxSignals ? ruleParams.maxSignals : totalHits; - - // number of docs in the current search result - let hitsSize = someResult.hits.hits.length; - logger.debug(`first size: ${hitsSize}`); - let sortIds = someResult.hits.hits[0].sort; - if (sortIds == null && totalHits > 0) { - logger.error('sortIds was empty on first search but expected more'); - return false; - } else if (sortIds == null && totalHits === 0) { - return true; - } - let sortId; - if (sortIds != null) { - sortId = sortIds[0]; - } - while (hitsSize < maxTotalHitsSize && hitsSize !== 0) { - try { - logger.debug(`sortIds: ${sortIds}`); - const searchAfterResult: SignalSearchResponse = await singleSearchAfter({ - searchAfterSortId: sortId, - ruleParams, - services, - logger, - filter, - pageSize, // maximum number of docs to receive per search result. - }); - if (searchAfterResult.hits.hits.length === 0) { - return true; - } - hitsSize += searchAfterResult.hits.hits.length; - logger.debug(`size adjusted: ${hitsSize}`); - sortIds = searchAfterResult.hits.hits[0].sort; - if (sortIds == null) { - logger.debug('sortIds was empty on search'); - return true; // no more search results - } - sortId = sortIds[0]; - logger.debug('next bulk index'); - await singleBulkCreate({ - someResult: searchAfterResult, - ruleParams, - services, - logger, - id, - signalsIndex, - name, - createdBy, - updatedBy, - interval, - enabled, - tags, - }); - logger.debug('finished next bulk index'); - } catch (exc) { - logger.error(`[-] search_after and bulk threw an error ${exc}`); - return false; - } - } - logger.debug(`[+] completed bulk index of ${maxTotalHitsSize}`); - return true; -}; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts index cd8b716221b9..978434859ef9 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts @@ -6,11 +6,13 @@ import { ServerInjectOptions } from 'hapi'; import { ActionResult } from '../../../../../../actions/server/types'; -import { RuleAlertParamsRest, RuleAlertType, SignalsRestParams } from '../../alerts/types'; +import { SignalsRestParams } from '../../signals/types'; import { DETECTION_ENGINE_RULES_URL, DETECTION_ENGINE_SIGNALS_STATUS_URL, } from '../../../../../common/constants'; +import { RuleAlertType } from '../../rules/types'; +import { RuleAlertParamsRest } from '../../types'; // The Omit of filter is because of a Hapi Server Typing issue that I am unclear // where it comes from. I would hope to remove the "filter" as an omit at some point diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/create_rules_route.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.test.ts similarity index 96% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/create_rules_route.test.ts rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.test.ts index b271af2db1e7..094449a5f61a 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/create_rules_route.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.test.ts @@ -9,7 +9,7 @@ import { createMockServerWithoutActionClientDecoration, createMockServerWithoutAlertClientDecoration, createMockServerWithoutActionOrAlertClientDecoration, -} from './__mocks__/_mock_server'; +} from '../__mocks__/_mock_server'; import { createRulesRoute } from './create_rules_route'; import { ServerInjectOptions } from 'hapi'; import { @@ -18,8 +18,8 @@ import { createActionResult, getCreateRequest, typicalPayload, -} from './__mocks__/request_responses'; -import { DETECTION_ENGINE_RULES_URL } from '../../../../common/constants'; +} from '../__mocks__/request_responses'; +import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; describe('create_rules', () => { let { server, alertsClient, actionsClient, elasticsearch } = createMockServer(); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/create_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.ts similarity index 85% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/create_rules_route.ts rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.ts index a137d5425018..0dc213e9e217 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/create_rules_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.ts @@ -8,14 +8,15 @@ import Hapi from 'hapi'; import { isFunction } from 'lodash/fp'; import Boom from 'boom'; import uuid from 'uuid'; -import { DETECTION_ENGINE_RULES_URL } from '../../../../common/constants'; -import { createRules } from '../alerts/create_rules'; -import { RulesRequest } from '../alerts/types'; -import { createRulesSchema } from './schemas'; -import { ServerFacade } from '../../../types'; -import { readRules } from '../alerts/read_rules'; -import { transformOrError, transformError, getIndex, callWithRequestFactory } from './utils'; -import { getIndexExists } from '../index/get_index_exists'; +import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; +import { createRules } from '../../rules/create_rules'; +import { RulesRequest } from '../../rules/types'; +import { createRulesSchema } from '../schemas/create_rules_schema'; +import { ServerFacade } from '../../../../types'; +import { readRules } from '../../rules/read_rules'; +import { transformOrError } from './utils'; +import { getIndexExists } from '../../index/get_index_exists'; +import { callWithRequestFactory, getIndex, transformError } from '../utils'; export const createCreateRulesRoute = (server: ServerFacade): Hapi.ServerRoute => { return { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/delete_rules_route.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_route.test.ts similarity index 96% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/delete_rules_route.test.ts rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_route.test.ts index 0808051964dc..cacafcf741e6 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/delete_rules_route.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_route.test.ts @@ -9,7 +9,7 @@ import { createMockServerWithoutActionClientDecoration, createMockServerWithoutAlertClientDecoration, createMockServerWithoutActionOrAlertClientDecoration, -} from './__mocks__/_mock_server'; +} from '../__mocks__/_mock_server'; import { deleteRulesRoute } from './delete_rules_route'; import { ServerInjectOptions } from 'hapi'; @@ -19,8 +19,8 @@ import { getDeleteRequest, getFindResultWithSingleHit, getDeleteRequestById, -} from './__mocks__/request_responses'; -import { DETECTION_ENGINE_RULES_URL } from '../../../../common/constants'; +} from '../__mocks__/request_responses'; +import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; describe('delete_rules', () => { let { server, alertsClient } = createMockServer(); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/delete_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_route.ts similarity index 78% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/delete_rules_route.ts rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_route.ts index fe8b139f11c0..c2b2e2fdbbae 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/delete_rules_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_route.ts @@ -7,12 +7,13 @@ import Hapi from 'hapi'; import { isFunction } from 'lodash/fp'; -import { DETECTION_ENGINE_RULES_URL } from '../../../../common/constants'; -import { deleteRules } from '../alerts/delete_rules'; -import { ServerFacade } from '../../../types'; -import { queryRulesSchema } from './schemas'; -import { QueryRequest } from '../alerts/types'; -import { getIdError, transformOrError, transformError } from './utils'; +import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; +import { deleteRules } from '../../rules/delete_rules'; +import { ServerFacade } from '../../../../types'; +import { queryRulesSchema } from '../schemas/query_rules_schema'; +import { getIdError, transformOrError } from './utils'; +import { transformError } from '../utils'; +import { QueryRequest } from './types'; export const createDeleteRulesRoute: Hapi.ServerRoute = { method: 'DELETE', diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/find_rules_route.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/find_rules_route.test.ts similarity index 94% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/find_rules_route.test.ts rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/find_rules_route.test.ts index dae40f05155d..38937c13d302 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/find_rules_route.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/find_rules_route.test.ts @@ -9,12 +9,12 @@ import { createMockServerWithoutActionClientDecoration, createMockServerWithoutAlertClientDecoration, createMockServerWithoutActionOrAlertClientDecoration, -} from './__mocks__/_mock_server'; +} from '../__mocks__/_mock_server'; import { findRulesRoute } from './find_rules_route'; import { ServerInjectOptions } from 'hapi'; -import { getFindResult, getResult, getFindRequest } from './__mocks__/request_responses'; -import { DETECTION_ENGINE_RULES_URL } from '../../../../common/constants'; +import { getFindResult, getResult, getFindRequest } from '../__mocks__/request_responses'; +import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; describe('find_rules', () => { let { server, alertsClient, actionsClient } = createMockServer(); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/find_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/find_rules_route.ts similarity index 78% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/find_rules_route.ts rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/find_rules_route.ts index 137dd9352699..6e89ddb19017 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/find_rules_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/find_rules_route.ts @@ -6,12 +6,13 @@ import Hapi from 'hapi'; import { isFunction } from 'lodash/fp'; -import { DETECTION_ENGINE_RULES_URL } from '../../../../common/constants'; -import { findRules } from '../alerts/find_rules'; -import { FindRulesRequest } from '../alerts/types'; -import { findRulesSchema } from './schemas'; -import { ServerFacade } from '../../../types'; -import { transformFindAlertsOrError, transformError } from './utils'; +import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; +import { findRules } from '../../rules/find_rules'; +import { FindRulesRequest } from '../../rules/types'; +import { findRulesSchema } from '../schemas/find_rules_schema'; +import { ServerFacade } from '../../../../types'; +import { transformFindAlertsOrError } from './utils'; +import { transformError } from '../utils'; export const createFindRulesRoute: Hapi.ServerRoute = { method: 'GET', diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/read_rules_route.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/read_rules_route.test.ts similarity index 94% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/read_rules_route.test.ts rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/read_rules_route.test.ts index 47ecf62f41be..0d77583573c1 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/read_rules_route.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/read_rules_route.test.ts @@ -9,7 +9,7 @@ import { createMockServerWithoutActionClientDecoration, createMockServerWithoutAlertClientDecoration, createMockServerWithoutActionOrAlertClientDecoration, -} from './__mocks__/_mock_server'; +} from '../__mocks__/_mock_server'; import { readRulesRoute } from './read_rules_route'; import { ServerInjectOptions } from 'hapi'; @@ -18,8 +18,8 @@ import { getResult, getReadRequest, getFindResultWithSingleHit, -} from './__mocks__/request_responses'; -import { DETECTION_ENGINE_RULES_URL } from '../../../../common/constants'; +} from '../__mocks__/request_responses'; +import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; describe('read_signals', () => { let { server, alertsClient } = createMockServer(); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/read_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/read_rules_route.ts similarity index 78% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/read_rules_route.ts rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/read_rules_route.ts index a7bda40fdc52..a842e68b6b7f 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/read_rules_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/read_rules_route.ts @@ -6,13 +6,14 @@ import Hapi from 'hapi'; import { isFunction } from 'lodash/fp'; -import { DETECTION_ENGINE_RULES_URL } from '../../../../common/constants'; -import { getIdError, transformOrError, transformError } from './utils'; +import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; +import { getIdError, transformOrError } from './utils'; +import { transformError } from '../utils'; -import { readRules } from '../alerts/read_rules'; -import { ServerFacade } from '../../../types'; -import { queryRulesSchema } from './schemas'; -import { QueryRequest } from '../alerts/types'; +import { readRules } from '../../rules/read_rules'; +import { ServerFacade } from '../../../../types'; +import { queryRulesSchema } from '../schemas/query_rules_schema'; +import { QueryRequest } from './types'; export const createReadRulesRoute: Hapi.ServerRoute = { method: 'GET', diff --git a/x-pack/legacy/plugins/watcher/server/routes/api/fields/index.js b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/types.ts similarity index 57% rename from x-pack/legacy/plugins/watcher/server/routes/api/fields/index.js rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/types.ts index 8474f8a614bf..f6878c9edc9b 100644 --- a/x-pack/legacy/plugins/watcher/server/routes/api/fields/index.js +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/types.ts @@ -4,4 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -export { registerFieldsRoutes } from './register_fields_routes'; +import { RequestFacade } from '../../../../types'; + +export type QueryRequest = Omit & { + query: { id: string | undefined; rule_id: string | undefined }; +}; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/update_rules_route.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_route.test.ts similarity index 97% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/update_rules_route.test.ts rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_route.test.ts index dfa1275a6b26..3cf5c07655d9 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/update_rules_route.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_route.test.ts @@ -9,7 +9,7 @@ import { createMockServerWithoutActionClientDecoration, createMockServerWithoutAlertClientDecoration, createMockServerWithoutActionOrAlertClientDecoration, -} from './__mocks__/_mock_server'; +} from '../__mocks__/_mock_server'; import { updateRulesRoute } from './update_rules_route'; import { ServerInjectOptions } from 'hapi'; @@ -20,8 +20,8 @@ import { getUpdateRequest, typicalPayload, getFindResultWithSingleHit, -} from './__mocks__/request_responses'; -import { DETECTION_ENGINE_RULES_URL } from '../../../../common/constants'; +} from '../__mocks__/request_responses'; +import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; describe('update_rules', () => { let { server, alertsClient, actionsClient } = createMockServer(); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/update_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_route.ts similarity index 84% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/update_rules_route.ts rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_route.ts index 943c41fd6dea..2e7b48afbb5d 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/update_rules_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_route.ts @@ -6,12 +6,13 @@ import Hapi from 'hapi'; import { isFunction } from 'lodash/fp'; -import { DETECTION_ENGINE_RULES_URL } from '../../../../common/constants'; -import { updateRules } from '../alerts/update_rules'; -import { UpdateRulesRequest } from '../alerts/types'; -import { updateRulesSchema } from './schemas'; -import { ServerFacade } from '../../../types'; -import { getIdError, transformOrError, transformError } from './utils'; +import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; +import { updateRules } from '../../rules/update_rules'; +import { UpdateRulesRequest } from '../../rules/types'; +import { updateRulesSchema } from '../schemas/update_rules_schema'; +import { ServerFacade } from '../../../../types'; +import { getIdError, transformOrError } from './utils'; +import { transformError } from '../utils'; export const createUpdateRulesRoute: Hapi.ServerRoute = { method: 'PUT', diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/utils.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/utils.test.ts new file mode 100644 index 000000000000..d4e129f543cc --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/utils.test.ts @@ -0,0 +1,496 @@ +/* + * 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 Boom from 'boom'; + +import { + transformAlertToRule, + getIdError, + transformFindAlertsOrError, + transformOrError, +} from './utils'; +import { getResult } from '../__mocks__/request_responses'; + +describe('utils', () => { + describe('transformAlertToRule', () => { + test('should work with a full data set', () => { + const fullRule = getResult(); + const rule = transformAlertToRule(fullRule); + expect(rule).toEqual({ + created_by: 'elastic', + description: 'Detecting root and admin users', + enabled: true, + false_positives: [], + from: 'now-6m', + id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + immutable: false, + index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + interval: '5m', + risk_score: 50, + rule_id: 'rule-1', + language: 'kuery', + max_signals: 100, + name: 'Detect Root/Admin Users', + output_index: '.siem-signals', + query: 'user.name: root or user.name: admin', + references: ['http://www.example.com', 'https://ww.example.com'], + severity: 'high', + updated_by: 'elastic', + tags: [], + threats: [ + { + framework: 'MITRE ATT&CK', + tactic: { + id: 'TA0040', + name: 'impact', + reference: 'https://attack.mitre.org/tactics/TA0040/', + }, + techniques: [ + { + id: 'T1499', + name: 'endpoint denial of service', + reference: 'https://attack.mitre.org/techniques/T1499/', + }, + ], + }, + ], + to: 'now', + type: 'query', + }); + }); + + test('should work with a partial data set missing data', () => { + const fullRule = getResult(); + const { from, language, ...omitData } = transformAlertToRule(fullRule); + expect(omitData).toEqual({ + created_by: 'elastic', + description: 'Detecting root and admin users', + enabled: true, + false_positives: [], + id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + immutable: false, + index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + output_index: '.siem-signals', + interval: '5m', + risk_score: 50, + rule_id: 'rule-1', + max_signals: 100, + name: 'Detect Root/Admin Users', + query: 'user.name: root or user.name: admin', + references: ['http://www.example.com', 'https://ww.example.com'], + severity: 'high', + updated_by: 'elastic', + tags: [], + threats: [ + { + framework: 'MITRE ATT&CK', + tactic: { + id: 'TA0040', + name: 'impact', + reference: 'https://attack.mitre.org/tactics/TA0040/', + }, + techniques: [ + { + id: 'T1499', + name: 'endpoint denial of service', + reference: 'https://attack.mitre.org/techniques/T1499/', + }, + ], + }, + ], + to: 'now', + type: 'query', + }); + }); + + test('should omit query if query is null', () => { + const fullRule = getResult(); + fullRule.params.query = null; + const rule = transformAlertToRule(fullRule); + expect(rule).toEqual({ + created_by: 'elastic', + description: 'Detecting root and admin users', + enabled: true, + false_positives: [], + from: 'now-6m', + id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + immutable: false, + index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + output_index: '.siem-signals', + interval: '5m', + risk_score: 50, + rule_id: 'rule-1', + language: 'kuery', + max_signals: 100, + name: 'Detect Root/Admin Users', + references: ['http://www.example.com', 'https://ww.example.com'], + severity: 'high', + updated_by: 'elastic', + tags: [], + threats: [ + { + framework: 'MITRE ATT&CK', + tactic: { + id: 'TA0040', + name: 'impact', + reference: 'https://attack.mitre.org/tactics/TA0040/', + }, + techniques: [ + { + id: 'T1499', + name: 'endpoint denial of service', + reference: 'https://attack.mitre.org/techniques/T1499/', + }, + ], + }, + ], + to: 'now', + type: 'query', + }); + }); + + test('should omit query if query is undefined', () => { + const fullRule = getResult(); + fullRule.params.query = undefined; + const rule = transformAlertToRule(fullRule); + expect(rule).toEqual({ + created_by: 'elastic', + description: 'Detecting root and admin users', + enabled: true, + false_positives: [], + from: 'now-6m', + id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + immutable: false, + index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + output_index: '.siem-signals', + interval: '5m', + rule_id: 'rule-1', + risk_score: 50, + language: 'kuery', + max_signals: 100, + name: 'Detect Root/Admin Users', + references: ['http://www.example.com', 'https://ww.example.com'], + severity: 'high', + updated_by: 'elastic', + tags: [], + threats: [ + { + framework: 'MITRE ATT&CK', + tactic: { + id: 'TA0040', + name: 'impact', + reference: 'https://attack.mitre.org/tactics/TA0040/', + }, + techniques: [ + { + id: 'T1499', + name: 'endpoint denial of service', + reference: 'https://attack.mitre.org/techniques/T1499/', + }, + ], + }, + ], + to: 'now', + type: 'query', + }); + }); + + test('should omit a mix of undefined, null, and missing fields', () => { + const fullRule = getResult(); + fullRule.params.query = undefined; + fullRule.params.language = null; + const { from, enabled, ...omitData } = transformAlertToRule(fullRule); + expect(omitData).toEqual({ + created_by: 'elastic', + description: 'Detecting root and admin users', + false_positives: [], + id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + immutable: false, + output_index: '.siem-signals', + index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + interval: '5m', + rule_id: 'rule-1', + risk_score: 50, + max_signals: 100, + name: 'Detect Root/Admin Users', + references: ['http://www.example.com', 'https://ww.example.com'], + severity: 'high', + updated_by: 'elastic', + tags: [], + threats: [ + { + framework: 'MITRE ATT&CK', + tactic: { + id: 'TA0040', + name: 'impact', + reference: 'https://attack.mitre.org/tactics/TA0040/', + }, + techniques: [ + { + id: 'T1499', + name: 'endpoint denial of service', + reference: 'https://attack.mitre.org/techniques/T1499/', + }, + ], + }, + ], + to: 'now', + type: 'query', + }); + }); + + test('should return enabled is equal to false', () => { + const fullRule = getResult(); + fullRule.enabled = false; + const ruleWithEnabledFalse = transformAlertToRule(fullRule); + expect(ruleWithEnabledFalse).toEqual({ + created_by: 'elastic', + description: 'Detecting root and admin users', + enabled: false, + from: 'now-6m', + false_positives: [], + id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + immutable: false, + output_index: '.siem-signals', + index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + interval: '5m', + language: 'kuery', + risk_score: 50, + rule_id: 'rule-1', + max_signals: 100, + name: 'Detect Root/Admin Users', + query: 'user.name: root or user.name: admin', + references: ['http://www.example.com', 'https://ww.example.com'], + severity: 'high', + updated_by: 'elastic', + tags: [], + threats: [ + { + framework: 'MITRE ATT&CK', + tactic: { + id: 'TA0040', + name: 'impact', + reference: 'https://attack.mitre.org/tactics/TA0040/', + }, + techniques: [ + { + id: 'T1499', + name: 'endpoint denial of service', + reference: 'https://attack.mitre.org/techniques/T1499/', + }, + ], + }, + ], + to: 'now', + type: 'query', + }); + }); + + test('should return immutable is equal to false', () => { + const fullRule = getResult(); + fullRule.params.immutable = false; + const ruleWithEnabledFalse = transformAlertToRule(fullRule); + expect(ruleWithEnabledFalse).toEqual({ + created_by: 'elastic', + description: 'Detecting root and admin users', + enabled: true, + from: 'now-6m', + false_positives: [], + id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + immutable: false, + output_index: '.siem-signals', + index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + interval: '5m', + language: 'kuery', + risk_score: 50, + rule_id: 'rule-1', + max_signals: 100, + name: 'Detect Root/Admin Users', + query: 'user.name: root or user.name: admin', + references: ['http://www.example.com', 'https://ww.example.com'], + severity: 'high', + updated_by: 'elastic', + tags: [], + threats: [ + { + framework: 'MITRE ATT&CK', + tactic: { + id: 'TA0040', + name: 'impact', + reference: 'https://attack.mitre.org/tactics/TA0040/', + }, + techniques: [ + { + id: 'T1499', + name: 'endpoint denial of service', + reference: 'https://attack.mitre.org/techniques/T1499/', + }, + ], + }, + ], + to: 'now', + type: 'query', + }); + }); + }); + + describe('getIdError', () => { + test('outputs message about id not being found if only id is defined and ruleId is undefined', () => { + const boom = getIdError({ id: '123', ruleId: undefined }); + expect(boom.message).toEqual('id: "123" not found'); + }); + + test('outputs message about id not being found if only id is defined and ruleId is null', () => { + const boom = getIdError({ id: '123', ruleId: null }); + expect(boom.message).toEqual('id: "123" not found'); + }); + + test('outputs message about ruleId not being found if only ruleId is defined and id is undefined', () => { + const boom = getIdError({ id: undefined, ruleId: 'rule-id-123' }); + expect(boom.message).toEqual('rule_id: "rule-id-123" not found'); + }); + + test('outputs message about ruleId not being found if only ruleId is defined and id is null', () => { + const boom = getIdError({ id: null, ruleId: 'rule-id-123' }); + expect(boom.message).toEqual('rule_id: "rule-id-123" not found'); + }); + + test('outputs message about both being not defined when both are undefined', () => { + const boom = getIdError({ id: undefined, ruleId: undefined }); + expect(boom.message).toEqual('id or rule_id should have been defined'); + }); + + test('outputs message about both being not defined when both are null', () => { + const boom = getIdError({ id: null, ruleId: null }); + expect(boom.message).toEqual('id or rule_id should have been defined'); + }); + + test('outputs message about both being not defined when id is null and ruleId is undefined', () => { + const boom = getIdError({ id: null, ruleId: undefined }); + expect(boom.message).toEqual('id or rule_id should have been defined'); + }); + + test('outputs message about both being not defined when id is undefined and ruleId is null', () => { + const boom = getIdError({ id: undefined, ruleId: null }); + expect(boom.message).toEqual('id or rule_id should have been defined'); + }); + }); + + describe('transformFindAlertsOrError', () => { + test('outputs empty data set when data set is empty correct', () => { + const output = transformFindAlertsOrError({ data: [] }); + expect(output).toEqual({ data: [] }); + }); + + test('outputs 200 if the data is of type siem alert', () => { + const output = transformFindAlertsOrError({ + data: [getResult()], + }); + expect(output).toEqual({ + data: [ + { + created_by: 'elastic', + description: 'Detecting root and admin users', + enabled: true, + false_positives: [], + from: 'now-6m', + id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + immutable: false, + output_index: '.siem-signals', + index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + interval: '5m', + risk_score: 50, + rule_id: 'rule-1', + language: 'kuery', + max_signals: 100, + name: 'Detect Root/Admin Users', + query: 'user.name: root or user.name: admin', + references: ['http://www.example.com', 'https://ww.example.com'], + severity: 'high', + updated_by: 'elastic', + tags: [], + to: 'now', + type: 'query', + threats: [ + { + framework: 'MITRE ATT&CK', + tactic: { + id: 'TA0040', + name: 'impact', + reference: 'https://attack.mitre.org/tactics/TA0040/', + }, + techniques: [ + { + id: 'T1499', + name: 'endpoint denial of service', + reference: 'https://attack.mitre.org/techniques/T1499/', + }, + ], + }, + ], + }, + ], + }); + }); + + test('returns 500 if the data is not of type siem alert', () => { + const output = transformFindAlertsOrError({ data: [{ random: 1 }] }); + expect((output as Boom).message).toEqual('Internal error transforming'); + }); + }); + + describe('transformOrError', () => { + test('outputs 200 if the data is of type siem alert', () => { + const output = transformOrError(getResult()); + expect(output).toEqual({ + created_by: 'elastic', + description: 'Detecting root and admin users', + enabled: true, + false_positives: [], + from: 'now-6m', + id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + immutable: false, + output_index: '.siem-signals', + index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + interval: '5m', + rule_id: 'rule-1', + risk_score: 50, + language: 'kuery', + max_signals: 100, + name: 'Detect Root/Admin Users', + query: 'user.name: root or user.name: admin', + references: ['http://www.example.com', 'https://ww.example.com'], + severity: 'high', + updated_by: 'elastic', + tags: [], + to: 'now', + type: 'query', + threats: [ + { + framework: 'MITRE ATT&CK', + tactic: { + id: 'TA0040', + name: 'impact', + reference: 'https://attack.mitre.org/tactics/TA0040/', + }, + techniques: [ + { + id: 'T1499', + name: 'endpoint denial of service', + reference: 'https://attack.mitre.org/techniques/T1499/', + }, + ], + }, + ], + }); + }); + + test('returns 500 if the data is not of type siem alert', () => { + const output = transformOrError({ data: [{ random: 1 }] }); + expect((output as Boom).message).toEqual('Internal error transforming'); + }); + }); +}); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/utils.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/utils.ts new file mode 100644 index 000000000000..c9ae3abdfdc6 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/utils.ts @@ -0,0 +1,76 @@ +/* + * 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 Boom from 'boom'; +import { pickBy } from 'lodash/fp'; +import { RuleAlertType, isAlertType, isAlertTypes } from '../../rules/types'; +import { OutputRuleAlertRest } from '../../types'; + +export const getIdError = ({ + id, + ruleId, +}: { + id: string | undefined | null; + ruleId: string | undefined | null; +}) => { + if (id != null) { + return new Boom(`id: "${id}" not found`, { statusCode: 404 }); + } else if (ruleId != null) { + return new Boom(`rule_id: "${ruleId}" not found`, { statusCode: 404 }); + } else { + return new Boom(`id or rule_id should have been defined`, { statusCode: 404 }); + } +}; + +// Transforms the data but will remove any null or undefined it encounters and not include +// those on the export +export const transformAlertToRule = (alert: RuleAlertType): Partial => { + return pickBy((value: unknown) => value != null, { + created_by: alert.createdBy, + description: alert.params.description, + enabled: alert.enabled, + false_positives: alert.params.falsePositives, + filters: alert.params.filters, + from: alert.params.from, + id: alert.id, + immutable: alert.params.immutable, + index: alert.params.index, + interval: alert.interval, + rule_id: alert.params.ruleId, + language: alert.params.language, + output_index: alert.params.outputIndex, + max_signals: alert.params.maxSignals, + risk_score: alert.params.riskScore, + name: alert.name, + query: alert.params.query, + references: alert.params.references, + saved_id: alert.params.savedId, + meta: alert.params.meta, + severity: alert.params.severity, + updated_by: alert.updatedBy, + tags: alert.tags, + to: alert.params.to, + type: alert.params.type, + threats: alert.params.threats, + }); +}; + +export const transformFindAlertsOrError = (findResults: { data: unknown[] }): unknown | Boom => { + if (isAlertTypes(findResults.data)) { + findResults.data = findResults.data.map(alert => transformAlertToRule(alert)); + return findResults; + } else { + return new Boom('Internal error transforming', { statusCode: 500 }); + } +}; + +export const transformOrError = (alert: unknown): Partial | Boom => { + if (isAlertType(alert)) { + return transformAlertToRule(alert); + } else { + return new Boom('Internal error transforming', { statusCode: 500 }); + } +}; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas.test.ts deleted file mode 100644 index f5147bc5a8f8..000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas.test.ts +++ /dev/null @@ -1,2133 +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 { - createRulesSchema, - updateRulesSchema, - findRulesSchema, - queryRulesSchema, - setSignalsStatusSchema, -} from './schemas'; -import { - RuleAlertParamsRest, - FindParamsRest, - UpdateRuleAlertParamsRest, - ThreatParams, - SignalsRestParams, -} from '../alerts/types'; - -describe('schemas', () => { - describe('create rules schema', () => { - test('empty objects do not validate', () => { - expect(createRulesSchema.validate>({}).error).toBeTruthy(); - }); - - test('made up values do not validate', () => { - expect( - createRulesSchema.validate>({ - madeUp: 'hi', - }).error - ).toBeTruthy(); - }); - - test('[rule_id] does not validate', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - }).error - ).toBeTruthy(); - }); - - test('[rule_id, description] does not validate', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - description: 'some description', - }).error - ).toBeTruthy(); - }); - - test('[rule_id, description, from] does not validate', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - }).error - ).toBeTruthy(); - }); - - test('[rule_id, description, from, to] does not validate', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - }).error - ).toBeTruthy(); - }); - - test('[rule_id, description, from, to, name] does not validate', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - name: 'some-name', - }).error - ).toBeTruthy(); - }); - - test('[rule_id, description, from, to, name, severity] does not validate', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - name: 'some-name', - severity: 'severity', - }).error - ).toBeTruthy(); - }); - - test('[rule_id, description, from, to, name, severity, type] does not validate', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - name: 'some-name', - severity: 'severity', - type: 'query', - }).error - ).toBeTruthy(); - }); - - test('[rule_id, description, from, to, name, severity, type, interval] does not validate', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - }).error - ).toBeTruthy(); - }); - - test('[rule_id, description, from, to, name, severity, type, interval, index] does not validate', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - name: 'some-name', - severity: 'severity', - type: 'query', - interval: '5m', - index: ['index-1'], - }).error - ).toBeTruthy(); - }); - - test('[rule_id, description, from, to, name, severity, type, query, index, interval] does validate', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - name: 'some-name', - severity: 'severity', - type: 'query', - query: 'some query', - index: ['index-1'], - interval: '5m', - }).error - ).toBeFalsy(); - }); - - test('[rule_id, description, from, to, index, name, severity, interval, type, query, language] does not validate', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - query: 'some query', - language: 'kuery', - }).error - ).toBeTruthy(); - }); - - test('[rule_id, description, from, to, index, name, severity, interval, type, query, language, risk_score] does validate', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - query: 'some query', - language: 'kuery', - }).error - ).toBeFalsy(); - }); - - test('[rule_id, description, from, to, index, name, severity, interval, type, query, language, risk_score, output_index] does validate', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - query: 'some query', - language: 'kuery', - }).error - ).toBeFalsy(); - }); - - test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score] does validate', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - risk_score: 50, - }).error - ).toBeFalsy(); - }); - - test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, output_index] does validate', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - }).error - ).toBeFalsy(); - }); - test('You can send in an empty array to threats', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - threats: [], - }).error - ).toBeFalsy(); - }); - test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, output_index, threats] does validate', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - threats: [ - { - framework: 'someFramework', - tactic: { - id: 'fakeId', - name: 'fakeName', - reference: 'fakeRef', - }, - techniques: [ - { - id: 'techniqueId', - name: 'techniqueName', - reference: 'techniqueRef', - }, - ], - }, - ], - }).error - ).toBeFalsy(); - }); - - test('allows references to be sent as valid', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - }).error - ).toBeFalsy(); - }); - - test('defaults references to an array', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - query: 'some-query', - language: 'kuery', - }).value.references - ).toEqual([]); - }); - - test('references cannot be numbers', () => { - expect( - createRulesSchema.validate< - Partial> & { references: number[] } - >({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - query: 'some-query', - language: 'kuery', - references: [5], - }).error - ).toBeTruthy(); - }); - - test('indexes cannot be numbers', () => { - expect( - createRulesSchema.validate< - Partial> & { index: number[] } - >({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: [5], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - query: 'some-query', - language: 'kuery', - }).error - ).toBeTruthy(); - }); - - test('defaults interval to 5 min', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - type: 'query', - }).value.interval - ).toEqual('5m'); - }); - - test('defaults max signals to 100', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - }).value.max_signals - ).toEqual(100); - }); - - test('saved_id is required when type is saved_query and will not validate without out', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'saved_query', - }).error - ).toBeTruthy(); - }); - - test('saved_id is required when type is saved_query and validates with it', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - risk_score: 50, - output_index: '.siem-signals', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'saved_query', - saved_id: 'some id', - }).error - ).toBeFalsy(); - }); - - test('saved_query type can have filters with it', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'saved_query', - saved_id: 'some id', - filters: [], - }).error - ).toBeFalsy(); - }); - - test('filters cannot be a string', () => { - expect( - createRulesSchema.validate< - Partial & { filters: string }> - >({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'saved_query', - saved_id: 'some id', - filters: 'some string', - }).error - ).toBeTruthy(); - }); - - test('language validates with kuery', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - }).error - ).toBeFalsy(); - }); - - test('language validates with lucene', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - risk_score: 50, - output_index: '.siem-signals', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'lucene', - }).error - ).toBeFalsy(); - }); - - test('language does not validate with something made up', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'something-made-up', - }).error - ).toBeTruthy(); - }); - - test('max_signals cannot be negative', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: -1, - }).error - ).toBeTruthy(); - }); - - test('max_signals cannot be zero', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 0, - }).error - ).toBeTruthy(); - }); - - test('max_signals can be 1', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - }).error - ).toBeFalsy(); - }); - - test('You can optionally send in an array of tags', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - tags: ['tag_1', 'tag_2'], - }).error - ).toBeFalsy(); - }); - - test('You cannot send in an array of tags that are numbers', () => { - expect( - createRulesSchema.validate> & { tags: number[] }>( - { - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - tags: [0, 1, 2], - } - ).error - ).toBeTruthy(); - }); - - test('You cannot send in an array of threats that are missing "framework"', () => { - expect( - createRulesSchema.validate< - Partial> & { - threats: Array>>; - } - >({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - threats: [ - { - tactic: { - id: 'fakeId', - name: 'fakeName', - reference: 'fakeRef', - }, - techniques: [ - { - id: 'techniqueId', - name: 'techniqueName', - reference: 'techniqueRef', - }, - ], - }, - ], - }).error - ).toBeTruthy(); - }); - test('You cannot send in an array of threats that are missing "tactic"', () => { - expect( - createRulesSchema.validate< - Partial> & { - threats: Array>>; - } - >({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - threats: [ - { - framework: 'fake', - techniques: [ - { - id: 'techniqueId', - name: 'techniqueName', - reference: 'techniqueRef', - }, - ], - }, - ], - }).error - ).toBeTruthy(); - }); - test('You cannot send in an array of threats that are missing "techniques"', () => { - expect( - createRulesSchema.validate< - Partial> & { - threats: Array>>; - } - >({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - threats: [ - { - framework: 'fake', - tactic: { - id: 'fakeId', - name: 'fakeName', - reference: 'fakeRef', - }, - }, - ], - }).error - ).toBeTruthy(); - }); - - test('You can optionally send in an array of false positives', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - false_positives: ['false_1', 'false_2'], - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - }).error - ).toBeFalsy(); - }); - - test('You cannot send in an array of false positives that are numbers', () => { - expect( - createRulesSchema.validate< - Partial> & { false_positives: number[] } - >({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - false_positives: [5, 4], - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - }).error - ).toBeTruthy(); - }); - - test('You can optionally set the immutable to be true', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - immutable: true, - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - }).error - ).toBeFalsy(); - }); - - test('You cannot set the immutable to be a number', () => { - expect( - createRulesSchema.validate< - Partial> & { immutable: number } - >({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - immutable: 5, - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - }).error - ).toBeTruthy(); - }); - - test('You cannot set the risk_score to 101', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 101, - description: 'some description', - from: 'now-5m', - to: 'now', - immutable: true, - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - }).error - ).toBeTruthy(); - }); - - test('You cannot set the risk_score to -1', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: -1, - description: 'some description', - from: 'now-5m', - to: 'now', - immutable: true, - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - }).error - ).toBeTruthy(); - }); - - test('You can set the risk_score to 0', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 0, - description: 'some description', - from: 'now-5m', - to: 'now', - immutable: true, - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - }).error - ).toBeFalsy(); - }); - - test('You can set the risk_score to 100', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 100, - description: 'some description', - from: 'now-5m', - to: 'now', - immutable: true, - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - }).error - ).toBeFalsy(); - }); - - test('You can set meta to any object you want', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - immutable: true, - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - meta: { - somethingMadeUp: { somethingElse: true }, - }, - }).error - ).toBeFalsy(); - }); - - test('You cannot create meta as a string', () => { - expect( - createRulesSchema.validate & { meta: string }>>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - immutable: true, - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - meta: 'should not work', - }).error - ).toBeTruthy(); - }); - - test('You can omit the query string when filters are present', () => { - expect( - createRulesSchema.validate & { meta: string }>>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - immutable: true, - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - language: 'kuery', - filters: [], - max_signals: 1, - }).error - ).toBeFalsy(); - }); - }); - - describe('update rules schema', () => { - test('empty objects do not validate as they require at least id or rule_id', () => { - expect(updateRulesSchema.validate>({}).error).toBeTruthy(); - }); - - test('made up values do not validate', () => { - expect( - updateRulesSchema.validate>({ - madeUp: 'hi', - }).error - ).toBeTruthy(); - }); - - test('[id] does validate', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - }).error - ).toBeFalsy(); - }); - - test('[rule_id] does validate', () => { - expect( - updateRulesSchema.validate>({ - rule_id: 'rule-1', - }).error - ).toBeFalsy(); - }); - - test('[id and rule_id] does not validate', () => { - expect( - updateRulesSchema.validate>({ - id: 'id-1', - rule_id: 'rule-1', - }).error - ).toBeTruthy(); - }); - - test('[rule_id, description] does validate', () => { - expect( - updateRulesSchema.validate>({ - rule_id: 'rule-1', - description: 'some description', - }).error - ).toBeFalsy(); - }); - - test('[id, description] does validate', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - }).error - ).toBeFalsy(); - }); - - test('[id, risk_score] does validate', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - risk_score: 10, - }).error - ).toBeFalsy(); - }); - - test('[rule_id, description, from] does validate', () => { - expect( - updateRulesSchema.validate>({ - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - }).error - ).toBeFalsy(); - }); - - test('[id, description, from] does validate', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - }).error - ).toBeFalsy(); - }); - - test('[rule_id, description, from, to] does validate', () => { - expect( - updateRulesSchema.validate>({ - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - }).error - ).toBeFalsy(); - }); - - test('[id, description, from, to] does validate', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - }).error - ).toBeFalsy(); - }); - - test('[rule_id, description, from, to, name] does validate', () => { - expect( - updateRulesSchema.validate>({ - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - name: 'some-name', - }).error - ).toBeFalsy(); - }); - - test('[id, description, from, to, name] does validate', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - name: 'some-name', - }).error - ).toBeFalsy(); - }); - - test('[rule_id, description, from, to, name, severity] does validate', () => { - expect( - updateRulesSchema.validate>({ - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - name: 'some-name', - severity: 'severity', - }).error - ).toBeFalsy(); - }); - - test('[id, description, from, to, name, severity] does validate', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - name: 'some-name', - severity: 'severity', - }).error - ).toBeFalsy(); - }); - - test('[rule_id, description, from, to, name, severity, type] does validate', () => { - expect( - updateRulesSchema.validate>({ - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - name: 'some-name', - severity: 'severity', - type: 'query', - }).error - ).toBeFalsy(); - }); - - test('[id, description, from, to, name, severity, type] does validate', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - name: 'some-name', - severity: 'severity', - type: 'query', - }).error - ).toBeFalsy(); - }); - - test('[rule_id, description, from, to, name, severity, type, interval] does validate', () => { - expect( - updateRulesSchema.validate>({ - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - }).error - ).toBeFalsy(); - }); - - test('[id, description, from, to, name, severity, type, interval] does validate', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - }).error - ).toBeFalsy(); - }); - - test('[rule_id, description, from, to, index, name, severity, interval, type] does validate', () => { - expect( - updateRulesSchema.validate>({ - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - }).error - ).toBeFalsy(); - }); - - test('[id, description, from, to, index, name, severity, interval, type] does validate', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - }).error - ).toBeFalsy(); - }); - - test('[rule_id, description, from, to, index, name, severity, interval, type, query] does validate', () => { - expect( - updateRulesSchema.validate>({ - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - query: 'some query', - }).error - ).toBeFalsy(); - }); - - test('[id, description, from, to, index, name, severity, interval, type, query] does validate', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - query: 'some query', - }).error - ).toBeFalsy(); - }); - - test('[rule_id, description, from, to, index, name, severity, interval, type, query, language] does validate', () => { - expect( - updateRulesSchema.validate>({ - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - query: 'some query', - language: 'kuery', - }).error - ).toBeFalsy(); - }); - - test('[id, description, from, to, index, name, severity, interval, type, query, language] does validate', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - query: 'some query', - language: 'kuery', - }).error - ).toBeFalsy(); - }); - - test('[rule_id, description, from, to, index, name, severity, type, filter] does validate', () => { - expect( - updateRulesSchema.validate>({ - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - }).error - ).toBeFalsy(); - }); - - test('[id, description, from, to, index, name, severity, type, filter] does validate', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - }).error - ).toBeFalsy(); - }); - - test('allows references to be sent as a valid value to update with', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - }).error - ).toBeFalsy(); - }); - - test('does not default references to an array', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - query: 'some-query', - language: 'kuery', - }).value.references - ).toEqual(undefined); - }); - - test('does not default interval', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - type: 'query', - }).value.interval - ).toEqual(undefined); - }); - - test('does not default max signal', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - }).value.max_signals - ).toEqual(undefined); - }); - - test('references cannot be numbers', () => { - expect( - updateRulesSchema.validate< - Partial> & { references: number[] } - >({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - query: 'some-query', - language: 'kuery', - references: [5], - }).error - ).toBeTruthy(); - }); - - test('indexes cannot be numbers', () => { - expect( - updateRulesSchema.validate< - Partial> & { index: number[] } - >({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: [5], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - query: 'some-query', - language: 'kuery', - }).error - ).toBeTruthy(); - }); - - test('saved_id is not required when type is saved_query and will validate without it', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'saved_query', - }).error - ).toBeFalsy(); - }); - - test('saved_id validates with saved_query', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'saved_query', - saved_id: 'some id', - }).error - ).toBeFalsy(); - }); - - test('saved_query type can have filters with it', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'saved_query', - saved_id: 'some id', - filters: [], - }).error - ).toBeFalsy(); - }); - - test('language validates with kuery', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - }).error - ).toBeFalsy(); - }); - - test('language validates with lucene', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'lucene', - }).error - ).toBeFalsy(); - }); - - test('language does not validate with something made up', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'something-made-up', - }).error - ).toBeTruthy(); - }); - - test('max_signals cannot be negative', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: -1, - }).error - ).toBeTruthy(); - }); - - test('max_signals cannot be zero', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 0, - }).error - ).toBeTruthy(); - }); - - test('max_signals can be 1', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - }).error - ).toBeFalsy(); - }); - - test('meta can be updated', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - meta: { whateverYouWant: 'anything_at_all' }, - }).error - ).toBeFalsy(); - }); - - test('You update meta as a string', () => { - expect( - updateRulesSchema.validate< - Partial & { meta: string }> - >({ - id: 'rule-1', - meta: 'should not work', - }).error - ).toBeTruthy(); - }); - - test('filters cannot be a string', () => { - expect( - updateRulesSchema.validate< - Partial & { filters: string }> - >({ - rule_id: 'rule-1', - type: 'query', - filters: 'some string', - }).error - ).toBeTruthy(); - }); - - test('threats is not defaulted to empty array on update', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - }).value.threats - ).toBe(undefined); - }); - - test('threats is not defaulted to undefined on update with empty array', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - threats: [], - }).value.threats - ).toMatchObject([]); - }); - test('threats is valid when updated with all sub-objects', () => { - const expected: ThreatParams[] = [ - { - framework: 'fake', - tactic: { - id: 'fakeId', - name: 'fakeName', - reference: 'fakeRef', - }, - techniques: [ - { - id: 'techniqueId', - name: 'techniqueName', - reference: 'techniqueRef', - }, - ], - }, - ]; - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - threats: [ - { - framework: 'fake', - tactic: { - id: 'fakeId', - name: 'fakeName', - reference: 'fakeRef', - }, - techniques: [ - { - id: 'techniqueId', - name: 'techniqueName', - reference: 'techniqueRef', - }, - ], - }, - ], - }).value.threats - ).toMatchObject(expected); - }); - test('threats is invalid when updated with missing property framework', () => { - expect( - updateRulesSchema.validate< - Partial> & { - threats: Array>>; - } - >({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - threats: [ - { - tactic: { - id: 'fakeId', - name: 'fakeName', - reference: 'fakeRef', - }, - techniques: [ - { - id: 'techniqueId', - name: 'techniqueName', - reference: 'techniqueRef', - }, - ], - }, - ], - }).error - ).toBeTruthy(); - }); - test('threats is invalid when updated with missing tactic sub-object', () => { - expect( - updateRulesSchema.validate< - Partial> & { - threats: Array>>; - } - >({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - threats: [ - { - framework: 'fake', - techniques: [ - { - id: 'techniqueId', - name: 'techniqueName', - reference: 'techniqueRef', - }, - ], - }, - ], - }).error - ).toBeTruthy(); - }); - test('threats is invalid when updated with missing techniques', () => { - expect( - updateRulesSchema.validate< - Partial> & { - threats: Array>>; - } - >({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - threats: [ - { - framework: 'fake', - tactic: { - id: 'techniqueId', - name: 'techniqueName', - reference: 'techniqueRef', - }, - }, - ], - }).error - ).toBeTruthy(); - }); - }); - - describe('find rules schema', () => { - test('empty objects do validate', () => { - expect(findRulesSchema.validate>({}).error).toBeFalsy(); - }); - - test('all values validate', () => { - expect( - findRulesSchema.validate>({ - per_page: 5, - page: 1, - sort_field: 'some field', - fields: ['field 1', 'field 2'], - filter: 'some filter', - sort_order: 'asc', - }).error - ).toBeFalsy(); - }); - - test('made up parameters do not validate', () => { - expect( - findRulesSchema.validate>({ - madeUp: 'hi', - }).error - ).toBeTruthy(); - }); - - test('per_page validates', () => { - expect( - findRulesSchema.validate>({ per_page: 5 }).error - ).toBeFalsy(); - }); - - test('page validates', () => { - expect( - findRulesSchema.validate>({ page: 5 }).error - ).toBeFalsy(); - }); - - test('sort_field validates', () => { - expect( - findRulesSchema.validate>({ sort_field: 'some value' }).error - ).toBeFalsy(); - }); - - test('fields validates with a string', () => { - expect( - findRulesSchema.validate>({ fields: ['some value'] }).error - ).toBeFalsy(); - }); - - test('fields validates with multiple strings', () => { - expect( - findRulesSchema.validate>({ - fields: ['some value 1', 'some value 2'], - }).error - ).toBeFalsy(); - }); - - test('fields does not validate with a number', () => { - expect( - findRulesSchema.validate> & { fields: number[] }>({ - fields: [5], - }).error - ).toBeTruthy(); - }); - - test('per page has a default of 20', () => { - expect(findRulesSchema.validate>({}).value.per_page).toEqual(20); - }); - - test('page has a default of 1', () => { - expect(findRulesSchema.validate>({}).value.page).toEqual(1); - }); - - test('filter works with a string', () => { - expect( - findRulesSchema.validate>({ - filter: 'some value 1', - }).error - ).toBeFalsy(); - }); - - test('filter does not work with a number', () => { - expect( - findRulesSchema.validate> & { filter: number }>({ - filter: 5, - }).error - ).toBeTruthy(); - }); - - test('sort_order requires sort_field to work', () => { - expect( - findRulesSchema.validate>({ - sort_order: 'asc', - }).error - ).toBeTruthy(); - }); - - test('sort_order and sort_field validate together', () => { - expect( - findRulesSchema.validate>({ - sort_order: 'asc', - sort_field: 'some field', - }).error - ).toBeFalsy(); - }); - - test('sort_order validates with desc and sort_field', () => { - expect( - findRulesSchema.validate>({ - sort_order: 'desc', - sort_field: 'some field', - }).error - ).toBeFalsy(); - }); - - test('sort_order does not validate with a string other than asc and desc', () => { - expect( - findRulesSchema.validate< - Partial> & { sort_order: string } - >({ - sort_order: 'some other string', - sort_field: 'some field', - }).error - ).toBeTruthy(); - }); - }); - - describe('queryRulesSchema', () => { - test('empty objects do not validate', () => { - expect(queryRulesSchema.validate>({}).error).toBeTruthy(); - }); - - test('both rule_id and id being supplied dot not validate', () => { - expect( - queryRulesSchema.validate>({ rule_id: '1', id: '1' }) - .error - ).toBeTruthy(); - }); - - test('only id validates', () => { - expect( - queryRulesSchema.validate>({ id: '1' }).error - ).toBeFalsy(); - }); - - test('only rule_id validates', () => { - expect( - queryRulesSchema.validate>({ rule_id: '1' }).error - ).toBeFalsy(); - }); - }); - - describe('set signal status schema', () => { - test('signal_ids and status is valid', () => { - expect( - setSignalsStatusSchema.validate>({ - signal_ids: ['somefakeid'], - status: 'open', - }).error - ).toBeFalsy(); - }); - - test('query and status is valid', () => { - expect( - setSignalsStatusSchema.validate>({ - query: {}, - status: 'open', - }).error - ).toBeFalsy(); - }); - - test('signal_ids and missing status is invalid', () => { - expect( - setSignalsStatusSchema.validate>({ - signal_ids: ['somefakeid'], - }).error - ).toBeTruthy(); - }); - - test('query and missing status is invalid', () => { - expect( - setSignalsStatusSchema.validate>({ - query: {}, - }).error - ).toBeTruthy(); - }); - - test('status is present but query or signal_ids is missing is invalid', () => { - expect( - setSignalsStatusSchema.validate>({ - status: 'closed', - }).error - ).toBeTruthy(); - }); - - test('signal_ids is present but status has wrong value', () => { - expect( - setSignalsStatusSchema.validate< - Partial< - Omit & { - status: string; - } - > - >({ - status: 'fakeVal', - }).error - ).toBeTruthy(); - }); - }); -}); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas.ts deleted file mode 100644 index 6ed6fdd2577d..000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas.ts +++ /dev/null @@ -1,162 +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 Joi from 'joi'; -import { DEFAULT_MAX_SIGNALS } from '../../../../common/constants'; - -/* eslint-disable @typescript-eslint/camelcase */ -const description = Joi.string(); -const enabled = Joi.boolean(); -const false_positives = Joi.array().items(Joi.string()); -const filters = Joi.array(); -const from = Joi.string(); -const immutable = Joi.boolean(); -const rule_id = Joi.string(); -const id = Joi.string(); -const index = Joi.array() - .items(Joi.string()) - .single(); -const interval = Joi.string(); -const query = Joi.string(); -const language = Joi.string().valid('kuery', 'lucene'); -const output_index = Joi.string(); -const saved_id = Joi.string(); -const meta = Joi.object(); -const max_signals = Joi.number().greater(0); -const name = Joi.string(); -const risk_score = Joi.number() - .greater(-1) - .less(101); -const severity = Joi.string(); -const status = Joi.string().valid('open', 'closed'); -const to = Joi.string(); -const type = Joi.string().valid('query', 'saved_query'); -const queryFilter = Joi.string(); -const references = Joi.array() - .items(Joi.string()) - .single(); -const per_page = Joi.number() - .min(0) - .default(20); -const page = Joi.number() - .min(1) - .default(1); -const signal_ids = Joi.array().items(Joi.string()); -const signal_status_query = Joi.object(); -const sort_field = Joi.string(); -const sort_order = Joi.string().valid('asc', 'desc'); -const tags = Joi.array().items(Joi.string()); -const fields = Joi.array() - .items(Joi.string()) - .single(); -const threat_framework = Joi.string(); -const threat_tactic_id = Joi.string(); -const threat_tactic_name = Joi.string(); -const threat_tactic_reference = Joi.string(); -const threat_tactic = Joi.object({ - id: threat_tactic_id.required(), - name: threat_tactic_name.required(), - reference: threat_tactic_reference.required(), -}); -const threat_technique_id = Joi.string(); -const threat_technique_name = Joi.string(); -const threat_technique_reference = Joi.string(); -const threat_technique = Joi.object({ - id: threat_technique_id.required(), - name: threat_technique_name.required(), - reference: threat_technique_reference.required(), -}); -const threat_techniques = Joi.array().items(threat_technique.required()); - -const threats = Joi.array().items( - Joi.object({ - framework: threat_framework.required(), - tactic: threat_tactic.required(), - techniques: threat_techniques.required(), - }) -); -/* eslint-enable @typescript-eslint/camelcase */ - -export const createRulesSchema = Joi.object({ - description: description.required(), - enabled: enabled.default(true), - false_positives: false_positives.default([]), - filters, - from: from.required(), - rule_id, - immutable: immutable.default(false), - index, - interval: interval.default('5m'), - query: query.allow('').default(''), - language: language.default('kuery'), - output_index, - saved_id: saved_id.when('type', { - is: 'saved_query', - then: Joi.required(), - otherwise: Joi.forbidden(), - }), - meta, - risk_score: risk_score.required(), - max_signals: max_signals.default(DEFAULT_MAX_SIGNALS), - name: name.required(), - severity: severity.required(), - tags: tags.default([]), - to: to.required(), - type: type.required(), - threats: threats.default([]), - references: references.default([]), -}); - -export const updateRulesSchema = Joi.object({ - description, - enabled, - false_positives, - filters, - from, - rule_id, - id, - immutable, - index, - interval, - query: query.allow(''), - language, - output_index, - saved_id, - meta, - risk_score, - max_signals, - name, - severity, - tags, - to, - type, - threats, - references, -}).xor('id', 'rule_id'); - -export const queryRulesSchema = Joi.object({ - rule_id, - id, -}).xor('id', 'rule_id'); - -export const findRulesSchema = Joi.object({ - fields, - filter: queryFilter, - per_page, - page, - sort_field: Joi.when(Joi.ref('sort_order'), { - is: Joi.exist(), - then: sort_field.required(), - otherwise: sort_field.optional(), - }), - sort_order, -}); - -export const setSignalsStatusSchema = Joi.object({ - signal_ids, - query: signal_status_query, - status: status.required(), -}).xor('signal_ids', 'query'); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.test.ts new file mode 100644 index 000000000000..4efea69db1f4 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.test.ts @@ -0,0 +1,1047 @@ +/* + * 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 { createRulesSchema } from './create_rules_schema'; +import { UpdateRuleAlertParamsRest } from '../../rules/types'; +import { ThreatParams, RuleAlertParamsRest } from '../../types'; + +describe('create rules schema', () => { + test('empty objects do not validate', () => { + expect(createRulesSchema.validate>({}).error).toBeTruthy(); + }); + + test('made up values do not validate', () => { + expect( + createRulesSchema.validate>({ + madeUp: 'hi', + }).error + ).toBeTruthy(); + }); + + test('[rule_id] does not validate', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + }).error + ).toBeTruthy(); + }); + + test('[rule_id, description] does not validate', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + }).error + ).toBeTruthy(); + }); + + test('[rule_id, description, from] does not validate', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + }).error + ).toBeTruthy(); + }); + + test('[rule_id, description, from, to] does not validate', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + }).error + ).toBeTruthy(); + }); + + test('[rule_id, description, from, to, name] does not validate', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + name: 'some-name', + }).error + ).toBeTruthy(); + }); + + test('[rule_id, description, from, to, name, severity] does not validate', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + name: 'some-name', + severity: 'severity', + }).error + ).toBeTruthy(); + }); + + test('[rule_id, description, from, to, name, severity, type] does not validate', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + name: 'some-name', + severity: 'severity', + type: 'query', + }).error + ).toBeTruthy(); + }); + + test('[rule_id, description, from, to, name, severity, type, interval] does not validate', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + }).error + ).toBeTruthy(); + }); + + test('[rule_id, description, from, to, name, severity, type, interval, index] does not validate', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + name: 'some-name', + severity: 'severity', + type: 'query', + interval: '5m', + index: ['index-1'], + }).error + ).toBeTruthy(); + }); + + test('[rule_id, description, from, to, name, severity, type, query, index, interval] does validate', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + name: 'some-name', + severity: 'severity', + type: 'query', + query: 'some query', + index: ['index-1'], + interval: '5m', + }).error + ).toBeFalsy(); + }); + + test('[rule_id, description, from, to, index, name, severity, interval, type, query, language] does not validate', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + query: 'some query', + language: 'kuery', + }).error + ).toBeTruthy(); + }); + + test('[rule_id, description, from, to, index, name, severity, interval, type, query, language, risk_score] does validate', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + query: 'some query', + language: 'kuery', + }).error + ).toBeFalsy(); + }); + + test('[rule_id, description, from, to, index, name, severity, interval, type, query, language, risk_score, output_index] does validate', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + query: 'some query', + language: 'kuery', + }).error + ).toBeFalsy(); + }); + + test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score] does validate', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + risk_score: 50, + }).error + ).toBeFalsy(); + }); + + test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, output_index] does validate', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + }).error + ).toBeFalsy(); + }); + test('You can send in an empty array to threats', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + threats: [], + }).error + ).toBeFalsy(); + }); + test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, output_index, threats] does validate', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + threats: [ + { + framework: 'someFramework', + tactic: { + id: 'fakeId', + name: 'fakeName', + reference: 'fakeRef', + }, + techniques: [ + { + id: 'techniqueId', + name: 'techniqueName', + reference: 'techniqueRef', + }, + ], + }, + ], + }).error + ).toBeFalsy(); + }); + + test('allows references to be sent as valid', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + }).error + ).toBeFalsy(); + }); + + test('defaults references to an array', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + query: 'some-query', + language: 'kuery', + }).value.references + ).toEqual([]); + }); + + test('references cannot be numbers', () => { + expect( + createRulesSchema.validate< + Partial> & { references: number[] } + >({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + query: 'some-query', + language: 'kuery', + references: [5], + }).error + ).toBeTruthy(); + }); + + test('indexes cannot be numbers', () => { + expect( + createRulesSchema.validate> & { index: number[] }>( + { + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: [5], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + query: 'some-query', + language: 'kuery', + } + ).error + ).toBeTruthy(); + }); + + test('defaults interval to 5 min', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + type: 'query', + }).value.interval + ).toEqual('5m'); + }); + + test('defaults max signals to 100', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + }).value.max_signals + ).toEqual(100); + }); + + test('saved_id is required when type is saved_query and will not validate without out', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'saved_query', + }).error + ).toBeTruthy(); + }); + + test('saved_id is required when type is saved_query and validates with it', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + risk_score: 50, + output_index: '.siem-signals', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'saved_query', + saved_id: 'some id', + }).error + ).toBeFalsy(); + }); + + test('saved_query type can have filters with it', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'saved_query', + saved_id: 'some id', + filters: [], + }).error + ).toBeFalsy(); + }); + + test('filters cannot be a string', () => { + expect( + createRulesSchema.validate< + Partial & { filters: string }> + >({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'saved_query', + saved_id: 'some id', + filters: 'some string', + }).error + ).toBeTruthy(); + }); + + test('language validates with kuery', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + }).error + ).toBeFalsy(); + }); + + test('language validates with lucene', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + risk_score: 50, + output_index: '.siem-signals', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'lucene', + }).error + ).toBeFalsy(); + }); + + test('language does not validate with something made up', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'something-made-up', + }).error + ).toBeTruthy(); + }); + + test('max_signals cannot be negative', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: -1, + }).error + ).toBeTruthy(); + }); + + test('max_signals cannot be zero', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 0, + }).error + ).toBeTruthy(); + }); + + test('max_signals can be 1', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + }).error + ).toBeFalsy(); + }); + + test('You can optionally send in an array of tags', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + tags: ['tag_1', 'tag_2'], + }).error + ).toBeFalsy(); + }); + + test('You cannot send in an array of tags that are numbers', () => { + expect( + createRulesSchema.validate> & { tags: number[] }>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + tags: [0, 1, 2], + }).error + ).toBeTruthy(); + }); + + test('You cannot send in an array of threats that are missing "framework"', () => { + expect( + createRulesSchema.validate< + Partial> & { + threats: Array>>; + } + >({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + threats: [ + { + tactic: { + id: 'fakeId', + name: 'fakeName', + reference: 'fakeRef', + }, + techniques: [ + { + id: 'techniqueId', + name: 'techniqueName', + reference: 'techniqueRef', + }, + ], + }, + ], + }).error + ).toBeTruthy(); + }); + test('You cannot send in an array of threats that are missing "tactic"', () => { + expect( + createRulesSchema.validate< + Partial> & { + threats: Array>>; + } + >({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + threats: [ + { + framework: 'fake', + techniques: [ + { + id: 'techniqueId', + name: 'techniqueName', + reference: 'techniqueRef', + }, + ], + }, + ], + }).error + ).toBeTruthy(); + }); + test('You cannot send in an array of threats that are missing "techniques"', () => { + expect( + createRulesSchema.validate< + Partial> & { + threats: Array>>; + } + >({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + threats: [ + { + framework: 'fake', + tactic: { + id: 'fakeId', + name: 'fakeName', + reference: 'fakeRef', + }, + }, + ], + }).error + ).toBeTruthy(); + }); + + test('You can optionally send in an array of false positives', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + false_positives: ['false_1', 'false_2'], + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + }).error + ).toBeFalsy(); + }); + + test('You cannot send in an array of false positives that are numbers', () => { + expect( + createRulesSchema.validate< + Partial> & { false_positives: number[] } + >({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + false_positives: [5, 4], + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + }).error + ).toBeTruthy(); + }); + + test('You can optionally set the immutable to be true', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + immutable: true, + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + }).error + ).toBeFalsy(); + }); + + test('You cannot set the immutable to be a number', () => { + expect( + createRulesSchema.validate< + Partial> & { immutable: number } + >({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + immutable: 5, + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + }).error + ).toBeTruthy(); + }); + + test('You cannot set the risk_score to 101', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 101, + description: 'some description', + from: 'now-5m', + to: 'now', + immutable: true, + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + }).error + ).toBeTruthy(); + }); + + test('You cannot set the risk_score to -1', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: -1, + description: 'some description', + from: 'now-5m', + to: 'now', + immutable: true, + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + }).error + ).toBeTruthy(); + }); + + test('You can set the risk_score to 0', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 0, + description: 'some description', + from: 'now-5m', + to: 'now', + immutable: true, + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + }).error + ).toBeFalsy(); + }); + + test('You can set the risk_score to 100', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 100, + description: 'some description', + from: 'now-5m', + to: 'now', + immutable: true, + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + }).error + ).toBeFalsy(); + }); + + test('You can set meta to any object you want', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + immutable: true, + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + meta: { + somethingMadeUp: { somethingElse: true }, + }, + }).error + ).toBeFalsy(); + }); + + test('You cannot create meta as a string', () => { + expect( + createRulesSchema.validate & { meta: string }>>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + immutable: true, + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + meta: 'should not work', + }).error + ).toBeTruthy(); + }); + + test('You can omit the query string when filters are present', () => { + expect( + createRulesSchema.validate & { meta: string }>>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + immutable: true, + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + language: 'kuery', + filters: [], + max_signals: 1, + }).error + ).toBeFalsy(); + }); +}); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.ts new file mode 100644 index 000000000000..ccda7256d2ee --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.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 Joi from 'joi'; + +/* eslint-disable @typescript-eslint/camelcase */ +import { + enabled, + description, + false_positives, + filters, + from, + immutable, + index, + rule_id, + interval, + query, + language, + output_index, + saved_id, + meta, + risk_score, + max_signals, + name, + severity, + tags, + to, + type, + threats, + references, +} from './schemas'; +/* eslint-enable @typescript-eslint/camelcase */ + +import { DEFAULT_MAX_SIGNALS } from '../../../../../common/constants'; + +export const createRulesSchema = Joi.object({ + description: description.required(), + enabled: enabled.default(true), + false_positives: false_positives.default([]), + filters, + from: from.required(), + rule_id, + immutable: immutable.default(false), + index, + interval: interval.default('5m'), + query: query.allow('').default(''), + language: language.default('kuery'), + output_index, + saved_id: saved_id.when('type', { + is: 'saved_query', + then: Joi.required(), + otherwise: Joi.forbidden(), + }), + meta, + risk_score: risk_score.required(), + max_signals: max_signals.default(DEFAULT_MAX_SIGNALS), + name: name.required(), + severity: severity.required(), + tags: tags.default([]), + to: to.required(), + type: type.required(), + threats: threats.default([]), + references: references.default([]), +}); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/find_rules_schema.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/find_rules_schema.test.ts new file mode 100644 index 000000000000..14b3bdb29873 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/find_rules_schema.test.ts @@ -0,0 +1,136 @@ +/* + * 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 { findRulesSchema } from './find_rules_schema'; +import { FindParamsRest } from '../../rules/types'; + +describe('find rules schema', () => { + test('empty objects do validate', () => { + expect(findRulesSchema.validate>({}).error).toBeFalsy(); + }); + + test('all values validate', () => { + expect( + findRulesSchema.validate>({ + per_page: 5, + page: 1, + sort_field: 'some field', + fields: ['field 1', 'field 2'], + filter: 'some filter', + sort_order: 'asc', + }).error + ).toBeFalsy(); + }); + + test('made up parameters do not validate', () => { + expect( + findRulesSchema.validate>({ + madeUp: 'hi', + }).error + ).toBeTruthy(); + }); + + test('per_page validates', () => { + expect( + findRulesSchema.validate>({ per_page: 5 }).error + ).toBeFalsy(); + }); + + test('page validates', () => { + expect( + findRulesSchema.validate>({ page: 5 }).error + ).toBeFalsy(); + }); + + test('sort_field validates', () => { + expect( + findRulesSchema.validate>({ sort_field: 'some value' }).error + ).toBeFalsy(); + }); + + test('fields validates with a string', () => { + expect( + findRulesSchema.validate>({ fields: ['some value'] }).error + ).toBeFalsy(); + }); + + test('fields validates with multiple strings', () => { + expect( + findRulesSchema.validate>({ + fields: ['some value 1', 'some value 2'], + }).error + ).toBeFalsy(); + }); + + test('fields does not validate with a number', () => { + expect( + findRulesSchema.validate> & { fields: number[] }>({ + fields: [5], + }).error + ).toBeTruthy(); + }); + + test('per page has a default of 20', () => { + expect(findRulesSchema.validate>({}).value.per_page).toEqual(20); + }); + + test('page has a default of 1', () => { + expect(findRulesSchema.validate>({}).value.page).toEqual(1); + }); + + test('filter works with a string', () => { + expect( + findRulesSchema.validate>({ + filter: 'some value 1', + }).error + ).toBeFalsy(); + }); + + test('filter does not work with a number', () => { + expect( + findRulesSchema.validate> & { filter: number }>({ + filter: 5, + }).error + ).toBeTruthy(); + }); + + test('sort_order requires sort_field to work', () => { + expect( + findRulesSchema.validate>({ + sort_order: 'asc', + }).error + ).toBeTruthy(); + }); + + test('sort_order and sort_field validate together', () => { + expect( + findRulesSchema.validate>({ + sort_order: 'asc', + sort_field: 'some field', + }).error + ).toBeFalsy(); + }); + + test('sort_order validates with desc and sort_field', () => { + expect( + findRulesSchema.validate>({ + sort_order: 'desc', + sort_field: 'some field', + }).error + ).toBeFalsy(); + }); + + test('sort_order does not validate with a string other than asc and desc', () => { + expect( + findRulesSchema.validate< + Partial> & { sort_order: string } + >({ + sort_order: 'some other string', + sort_field: 'some field', + }).error + ).toBeTruthy(); + }); +}); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/find_rules_schema.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/find_rules_schema.ts new file mode 100644 index 000000000000..3cc5b9ca4453 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/find_rules_schema.ts @@ -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 Joi from 'joi'; + +/* eslint-disable @typescript-eslint/camelcase */ +import { queryFilter, fields, per_page, page, sort_field, sort_order } from './schemas'; +/* eslint-enable @typescript-eslint/camelcase */ + +export const findRulesSchema = Joi.object({ + fields, + filter: queryFilter, + per_page, + page, + sort_field: Joi.when(Joi.ref('sort_order'), { + is: Joi.exist(), + then: sort_field.required(), + otherwise: sort_field.optional(), + }), + sort_order, +}); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/query_rules_schema.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/query_rules_schema.test.ts new file mode 100644 index 000000000000..6c4e96abd2b9 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/query_rules_schema.test.ts @@ -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 { queryRulesSchema } from './query_rules_schema'; +import { UpdateRuleAlertParamsRest } from '../../rules/types'; + +describe('queryRulesSchema', () => { + test('empty objects do not validate', () => { + expect(queryRulesSchema.validate>({}).error).toBeTruthy(); + }); + + test('both rule_id and id being supplied dot not validate', () => { + expect( + queryRulesSchema.validate>({ rule_id: '1', id: '1' }).error + ).toBeTruthy(); + }); + + test('only id validates', () => { + expect( + queryRulesSchema.validate>({ id: '1' }).error + ).toBeFalsy(); + }); + + test('only rule_id validates', () => { + expect( + queryRulesSchema.validate>({ rule_id: '1' }).error + ).toBeFalsy(); + }); +}); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/query_rules_schema.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/query_rules_schema.ts new file mode 100644 index 000000000000..86a731699d1e --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/query_rules_schema.ts @@ -0,0 +1,16 @@ +/* + * 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 Joi from 'joi'; + +/* eslint-disable @typescript-eslint/camelcase */ +import { rule_id, id } from './schemas'; +/* eslint-enable @typescript-eslint/camelcase */ + +export const queryRulesSchema = Joi.object({ + rule_id, + id, +}).xor('id', 'rule_id'); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/schemas.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/schemas.ts new file mode 100644 index 000000000000..5ab8ea3b8af3 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/schemas.ts @@ -0,0 +1,79 @@ +/* + * 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 Joi from 'joi'; + +/* eslint-disable @typescript-eslint/camelcase */ +export const description = Joi.string(); +export const enabled = Joi.boolean(); +export const false_positives = Joi.array().items(Joi.string()); +export const filters = Joi.array(); +export const from = Joi.string(); +export const immutable = Joi.boolean(); +export const rule_id = Joi.string(); +export const id = Joi.string(); +export const index = Joi.array() + .items(Joi.string()) + .single(); +export const interval = Joi.string(); +export const query = Joi.string(); +export const language = Joi.string().valid('kuery', 'lucene'); +export const output_index = Joi.string(); +export const saved_id = Joi.string(); +export const meta = Joi.object(); +export const max_signals = Joi.number().greater(0); +export const name = Joi.string(); +export const risk_score = Joi.number() + .greater(-1) + .less(101); +export const severity = Joi.string(); +export const status = Joi.string().valid('open', 'closed'); +export const to = Joi.string(); +export const type = Joi.string().valid('query', 'saved_query'); +export const queryFilter = Joi.string(); +export const references = Joi.array() + .items(Joi.string()) + .single(); +export const per_page = Joi.number() + .min(0) + .default(20); +export const page = Joi.number() + .min(1) + .default(1); +export const signal_ids = Joi.array().items(Joi.string()); +export const signal_status_query = Joi.object(); +export const sort_field = Joi.string(); +export const sort_order = Joi.string().valid('asc', 'desc'); +export const tags = Joi.array().items(Joi.string()); +export const fields = Joi.array() + .items(Joi.string()) + .single(); +export const threat_framework = Joi.string(); +export const threat_tactic_id = Joi.string(); +export const threat_tactic_name = Joi.string(); +export const threat_tactic_reference = Joi.string(); +export const threat_tactic = Joi.object({ + id: threat_tactic_id.required(), + name: threat_tactic_name.required(), + reference: threat_tactic_reference.required(), +}); +export const threat_technique_id = Joi.string(); +export const threat_technique_name = Joi.string(); +export const threat_technique_reference = Joi.string(); +export const threat_technique = Joi.object({ + id: threat_technique_id.required(), + name: threat_technique_name.required(), + reference: threat_technique_reference.required(), +}); +export const threat_techniques = Joi.array().items(threat_technique.required()); + +export const threats = Joi.array().items( + Joi.object({ + framework: threat_framework.required(), + tactic: threat_tactic.required(), + techniques: threat_techniques.required(), + }) +); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/set_signal_status_schema.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/set_signal_status_schema.test.ts new file mode 100644 index 000000000000..b586b4666bfe --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/set_signal_status_schema.test.ts @@ -0,0 +1,66 @@ +/* + * 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 { setSignalsStatusSchema } from './set_signal_status_schema'; +import { SignalsRestParams } from '../../signals/types'; + +describe('set signal status schema', () => { + test('signal_ids and status is valid', () => { + expect( + setSignalsStatusSchema.validate>({ + signal_ids: ['somefakeid'], + status: 'open', + }).error + ).toBeFalsy(); + }); + + test('query and status is valid', () => { + expect( + setSignalsStatusSchema.validate>({ + query: {}, + status: 'open', + }).error + ).toBeFalsy(); + }); + + test('signal_ids and missing status is invalid', () => { + expect( + setSignalsStatusSchema.validate>({ + signal_ids: ['somefakeid'], + }).error + ).toBeTruthy(); + }); + + test('query and missing status is invalid', () => { + expect( + setSignalsStatusSchema.validate>({ + query: {}, + }).error + ).toBeTruthy(); + }); + + test('status is present but query or signal_ids is missing is invalid', () => { + expect( + setSignalsStatusSchema.validate>({ + status: 'closed', + }).error + ).toBeTruthy(); + }); + + test('signal_ids is present but status has wrong value', () => { + expect( + setSignalsStatusSchema.validate< + Partial< + Omit & { + status: string; + } + > + >({ + status: 'fakeVal', + }).error + ).toBeTruthy(); + }); +}); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/set_signal_status_schema.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/set_signal_status_schema.ts new file mode 100644 index 000000000000..c8a06619287d --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/set_signal_status_schema.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import Joi from 'joi'; + +/* eslint-disable @typescript-eslint/camelcase */ +import { signal_ids, signal_status_query, status } from './schemas'; +/* eslint-enable @typescript-eslint/camelcase */ + +export const setSignalsStatusSchema = Joi.object({ + signal_ids, + query: signal_status_query, + status: status.required(), +}).xor('signal_ids', 'query'); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/update_rules_schema.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/update_rules_schema.test.ts new file mode 100644 index 000000000000..606a30309b2a --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/update_rules_schema.test.ts @@ -0,0 +1,869 @@ +/* + * 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 { updateRulesSchema } from './update_rules_schema'; +import { UpdateRuleAlertParamsRest } from '../../rules/types'; +import { ThreatParams } from '../../types'; + +describe('update rules schema', () => { + test('empty objects do not validate as they require at least id or rule_id', () => { + expect(updateRulesSchema.validate>({}).error).toBeTruthy(); + }); + + test('made up values do not validate', () => { + expect( + updateRulesSchema.validate>({ + madeUp: 'hi', + }).error + ).toBeTruthy(); + }); + + test('[id] does validate', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + }).error + ).toBeFalsy(); + }); + + test('[rule_id] does validate', () => { + expect( + updateRulesSchema.validate>({ + rule_id: 'rule-1', + }).error + ).toBeFalsy(); + }); + + test('[id and rule_id] does not validate', () => { + expect( + updateRulesSchema.validate>({ + id: 'id-1', + rule_id: 'rule-1', + }).error + ).toBeTruthy(); + }); + + test('[rule_id, description] does validate', () => { + expect( + updateRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + }).error + ).toBeFalsy(); + }); + + test('[id, description] does validate', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + }).error + ).toBeFalsy(); + }); + + test('[id, risk_score] does validate', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + risk_score: 10, + }).error + ).toBeFalsy(); + }); + + test('[rule_id, description, from] does validate', () => { + expect( + updateRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + }).error + ).toBeFalsy(); + }); + + test('[id, description, from] does validate', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + }).error + ).toBeFalsy(); + }); + + test('[rule_id, description, from, to] does validate', () => { + expect( + updateRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + }).error + ).toBeFalsy(); + }); + + test('[id, description, from, to] does validate', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + }).error + ).toBeFalsy(); + }); + + test('[rule_id, description, from, to, name] does validate', () => { + expect( + updateRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + name: 'some-name', + }).error + ).toBeFalsy(); + }); + + test('[id, description, from, to, name] does validate', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + name: 'some-name', + }).error + ).toBeFalsy(); + }); + + test('[rule_id, description, from, to, name, severity] does validate', () => { + expect( + updateRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + name: 'some-name', + severity: 'severity', + }).error + ).toBeFalsy(); + }); + + test('[id, description, from, to, name, severity] does validate', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + name: 'some-name', + severity: 'severity', + }).error + ).toBeFalsy(); + }); + + test('[rule_id, description, from, to, name, severity, type] does validate', () => { + expect( + updateRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + name: 'some-name', + severity: 'severity', + type: 'query', + }).error + ).toBeFalsy(); + }); + + test('[id, description, from, to, name, severity, type] does validate', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + name: 'some-name', + severity: 'severity', + type: 'query', + }).error + ).toBeFalsy(); + }); + + test('[rule_id, description, from, to, name, severity, type, interval] does validate', () => { + expect( + updateRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + }).error + ).toBeFalsy(); + }); + + test('[id, description, from, to, name, severity, type, interval] does validate', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + }).error + ).toBeFalsy(); + }); + + test('[rule_id, description, from, to, index, name, severity, interval, type] does validate', () => { + expect( + updateRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + }).error + ).toBeFalsy(); + }); + + test('[id, description, from, to, index, name, severity, interval, type] does validate', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + }).error + ).toBeFalsy(); + }); + + test('[rule_id, description, from, to, index, name, severity, interval, type, query] does validate', () => { + expect( + updateRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + query: 'some query', + }).error + ).toBeFalsy(); + }); + + test('[id, description, from, to, index, name, severity, interval, type, query] does validate', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + query: 'some query', + }).error + ).toBeFalsy(); + }); + + test('[rule_id, description, from, to, index, name, severity, interval, type, query, language] does validate', () => { + expect( + updateRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + query: 'some query', + language: 'kuery', + }).error + ).toBeFalsy(); + }); + + test('[id, description, from, to, index, name, severity, interval, type, query, language] does validate', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + query: 'some query', + language: 'kuery', + }).error + ).toBeFalsy(); + }); + + test('[rule_id, description, from, to, index, name, severity, type, filter] does validate', () => { + expect( + updateRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + }).error + ).toBeFalsy(); + }); + + test('[id, description, from, to, index, name, severity, type, filter] does validate', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + }).error + ).toBeFalsy(); + }); + + test('allows references to be sent as a valid value to update with', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + }).error + ).toBeFalsy(); + }); + + test('does not default references to an array', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + query: 'some-query', + language: 'kuery', + }).value.references + ).toEqual(undefined); + }); + + test('does not default interval', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + type: 'query', + }).value.interval + ).toEqual(undefined); + }); + + test('does not default max signal', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + }).value.max_signals + ).toEqual(undefined); + }); + + test('references cannot be numbers', () => { + expect( + updateRulesSchema.validate< + Partial> & { references: number[] } + >({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + query: 'some-query', + language: 'kuery', + references: [5], + }).error + ).toBeTruthy(); + }); + + test('indexes cannot be numbers', () => { + expect( + updateRulesSchema.validate< + Partial> & { index: number[] } + >({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: [5], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + query: 'some-query', + language: 'kuery', + }).error + ).toBeTruthy(); + }); + + test('saved_id is not required when type is saved_query and will validate without it', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'saved_query', + }).error + ).toBeFalsy(); + }); + + test('saved_id validates with saved_query', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'saved_query', + saved_id: 'some id', + }).error + ).toBeFalsy(); + }); + + test('saved_query type can have filters with it', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'saved_query', + saved_id: 'some id', + filters: [], + }).error + ).toBeFalsy(); + }); + + test('language validates with kuery', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + }).error + ).toBeFalsy(); + }); + + test('language validates with lucene', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'lucene', + }).error + ).toBeFalsy(); + }); + + test('language does not validate with something made up', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'something-made-up', + }).error + ).toBeTruthy(); + }); + + test('max_signals cannot be negative', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: -1, + }).error + ).toBeTruthy(); + }); + + test('max_signals cannot be zero', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 0, + }).error + ).toBeTruthy(); + }); + + test('max_signals can be 1', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + }).error + ).toBeFalsy(); + }); + + test('meta can be updated', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + meta: { whateverYouWant: 'anything_at_all' }, + }).error + ).toBeFalsy(); + }); + + test('You update meta as a string', () => { + expect( + updateRulesSchema.validate< + Partial & { meta: string }> + >({ + id: 'rule-1', + meta: 'should not work', + }).error + ).toBeTruthy(); + }); + + test('filters cannot be a string', () => { + expect( + updateRulesSchema.validate< + Partial & { filters: string }> + >({ + rule_id: 'rule-1', + type: 'query', + filters: 'some string', + }).error + ).toBeTruthy(); + }); + + test('threats is not defaulted to empty array on update', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + }).value.threats + ).toBe(undefined); + }); + + test('threats is not defaulted to undefined on update with empty array', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + threats: [], + }).value.threats + ).toMatchObject([]); + }); + test('threats is valid when updated with all sub-objects', () => { + const expected: ThreatParams[] = [ + { + framework: 'fake', + tactic: { + id: 'fakeId', + name: 'fakeName', + reference: 'fakeRef', + }, + techniques: [ + { + id: 'techniqueId', + name: 'techniqueName', + reference: 'techniqueRef', + }, + ], + }, + ]; + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + threats: [ + { + framework: 'fake', + tactic: { + id: 'fakeId', + name: 'fakeName', + reference: 'fakeRef', + }, + techniques: [ + { + id: 'techniqueId', + name: 'techniqueName', + reference: 'techniqueRef', + }, + ], + }, + ], + }).value.threats + ).toMatchObject(expected); + }); + test('threats is invalid when updated with missing property framework', () => { + expect( + updateRulesSchema.validate< + Partial> & { + threats: Array>>; + } + >({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + threats: [ + { + tactic: { + id: 'fakeId', + name: 'fakeName', + reference: 'fakeRef', + }, + techniques: [ + { + id: 'techniqueId', + name: 'techniqueName', + reference: 'techniqueRef', + }, + ], + }, + ], + }).error + ).toBeTruthy(); + }); + test('threats is invalid when updated with missing tactic sub-object', () => { + expect( + updateRulesSchema.validate< + Partial> & { + threats: Array>>; + } + >({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + threats: [ + { + framework: 'fake', + techniques: [ + { + id: 'techniqueId', + name: 'techniqueName', + reference: 'techniqueRef', + }, + ], + }, + ], + }).error + ).toBeTruthy(); + }); + test('threats is invalid when updated with missing techniques', () => { + expect( + updateRulesSchema.validate< + Partial> & { + threats: Array>>; + } + >({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + threats: [ + { + framework: 'fake', + tactic: { + id: 'techniqueId', + name: 'techniqueName', + reference: 'techniqueRef', + }, + }, + ], + }).error + ).toBeTruthy(); + }); +}); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/update_rules_schema.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/update_rules_schema.ts new file mode 100644 index 000000000000..244d8d1f5cc7 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/update_rules_schema.ts @@ -0,0 +1,63 @@ +/* + * 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 Joi from 'joi'; + +/* eslint-disable @typescript-eslint/camelcase */ +import { + enabled, + description, + false_positives, + filters, + from, + immutable, + index, + rule_id, + interval, + query, + language, + output_index, + saved_id, + meta, + risk_score, + max_signals, + name, + severity, + tags, + to, + type, + threats, + references, + id, +} from './schemas'; +/* eslint-enable @typescript-eslint/camelcase */ + +export const updateRulesSchema = Joi.object({ + description, + enabled, + false_positives, + filters, + from, + rule_id, + id, + immutable, + index, + interval, + query: query.allow(''), + language, + output_index, + saved_id, + meta, + risk_score, + max_signals, + name, + severity, + tags, + to, + type, + threats, + references, +}).xor('id', 'rule_id'); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/open_close_signals_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/open_close_signals_route.ts index 99af43ce51a1..b342cc5cd14e 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/open_close_signals_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/open_close_signals_route.ts @@ -6,8 +6,8 @@ import Hapi from 'hapi'; import { DETECTION_ENGINE_SIGNALS_STATUS_URL } from '../../../../../common/constants'; -import { SignalsRequest } from '../../alerts/types'; -import { setSignalsStatusSchema } from '../schemas'; +import { SignalsRequest } from '../../signals/types'; +import { setSignalsStatusSchema } from '../schemas/set_signal_status_schema'; import { ServerFacade } from '../../../../types'; import { transformError, getIndex } from '../utils'; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.test.ts index 4663ea357f25..8ca5c24d8810 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.test.ts @@ -6,495 +6,9 @@ import Boom from 'boom'; -import { - transformAlertToRule, - getIdError, - transformFindAlertsOrError, - transformOrError, - transformError, -} from './utils'; -import { getResult } from './__mocks__/request_responses'; +import { transformError } from './utils'; describe('utils', () => { - describe('transformAlertToRule', () => { - test('should work with a full data set', () => { - const fullRule = getResult(); - const rule = transformAlertToRule(fullRule); - expect(rule).toEqual({ - created_by: 'elastic', - description: 'Detecting root and admin users', - enabled: true, - false_positives: [], - from: 'now-6m', - id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', - immutable: false, - index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], - interval: '5m', - risk_score: 50, - rule_id: 'rule-1', - language: 'kuery', - max_signals: 100, - name: 'Detect Root/Admin Users', - output_index: '.siem-signals', - query: 'user.name: root or user.name: admin', - references: ['http://www.example.com', 'https://ww.example.com'], - severity: 'high', - updated_by: 'elastic', - tags: [], - threats: [ - { - framework: 'MITRE ATT&CK', - tactic: { - id: 'TA0040', - name: 'impact', - reference: 'https://attack.mitre.org/tactics/TA0040/', - }, - techniques: [ - { - id: 'T1499', - name: 'endpoint denial of service', - reference: 'https://attack.mitre.org/techniques/T1499/', - }, - ], - }, - ], - to: 'now', - type: 'query', - }); - }); - - test('should work with a partial data set missing data', () => { - const fullRule = getResult(); - const { from, language, ...omitData } = transformAlertToRule(fullRule); - expect(omitData).toEqual({ - created_by: 'elastic', - description: 'Detecting root and admin users', - enabled: true, - false_positives: [], - id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', - immutable: false, - index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], - output_index: '.siem-signals', - interval: '5m', - risk_score: 50, - rule_id: 'rule-1', - max_signals: 100, - name: 'Detect Root/Admin Users', - query: 'user.name: root or user.name: admin', - references: ['http://www.example.com', 'https://ww.example.com'], - severity: 'high', - updated_by: 'elastic', - tags: [], - threats: [ - { - framework: 'MITRE ATT&CK', - tactic: { - id: 'TA0040', - name: 'impact', - reference: 'https://attack.mitre.org/tactics/TA0040/', - }, - techniques: [ - { - id: 'T1499', - name: 'endpoint denial of service', - reference: 'https://attack.mitre.org/techniques/T1499/', - }, - ], - }, - ], - to: 'now', - type: 'query', - }); - }); - - test('should omit query if query is null', () => { - const fullRule = getResult(); - fullRule.params.query = null; - const rule = transformAlertToRule(fullRule); - expect(rule).toEqual({ - created_by: 'elastic', - description: 'Detecting root and admin users', - enabled: true, - false_positives: [], - from: 'now-6m', - id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', - immutable: false, - index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], - output_index: '.siem-signals', - interval: '5m', - risk_score: 50, - rule_id: 'rule-1', - language: 'kuery', - max_signals: 100, - name: 'Detect Root/Admin Users', - references: ['http://www.example.com', 'https://ww.example.com'], - severity: 'high', - updated_by: 'elastic', - tags: [], - threats: [ - { - framework: 'MITRE ATT&CK', - tactic: { - id: 'TA0040', - name: 'impact', - reference: 'https://attack.mitre.org/tactics/TA0040/', - }, - techniques: [ - { - id: 'T1499', - name: 'endpoint denial of service', - reference: 'https://attack.mitre.org/techniques/T1499/', - }, - ], - }, - ], - to: 'now', - type: 'query', - }); - }); - - test('should omit query if query is undefined', () => { - const fullRule = getResult(); - fullRule.params.query = undefined; - const rule = transformAlertToRule(fullRule); - expect(rule).toEqual({ - created_by: 'elastic', - description: 'Detecting root and admin users', - enabled: true, - false_positives: [], - from: 'now-6m', - id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', - immutable: false, - index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], - output_index: '.siem-signals', - interval: '5m', - rule_id: 'rule-1', - risk_score: 50, - language: 'kuery', - max_signals: 100, - name: 'Detect Root/Admin Users', - references: ['http://www.example.com', 'https://ww.example.com'], - severity: 'high', - updated_by: 'elastic', - tags: [], - threats: [ - { - framework: 'MITRE ATT&CK', - tactic: { - id: 'TA0040', - name: 'impact', - reference: 'https://attack.mitre.org/tactics/TA0040/', - }, - techniques: [ - { - id: 'T1499', - name: 'endpoint denial of service', - reference: 'https://attack.mitre.org/techniques/T1499/', - }, - ], - }, - ], - to: 'now', - type: 'query', - }); - }); - - test('should omit a mix of undefined, null, and missing fields', () => { - const fullRule = getResult(); - fullRule.params.query = undefined; - fullRule.params.language = null; - const { from, enabled, ...omitData } = transformAlertToRule(fullRule); - expect(omitData).toEqual({ - created_by: 'elastic', - description: 'Detecting root and admin users', - false_positives: [], - id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', - immutable: false, - output_index: '.siem-signals', - index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], - interval: '5m', - rule_id: 'rule-1', - risk_score: 50, - max_signals: 100, - name: 'Detect Root/Admin Users', - references: ['http://www.example.com', 'https://ww.example.com'], - severity: 'high', - updated_by: 'elastic', - tags: [], - threats: [ - { - framework: 'MITRE ATT&CK', - tactic: { - id: 'TA0040', - name: 'impact', - reference: 'https://attack.mitre.org/tactics/TA0040/', - }, - techniques: [ - { - id: 'T1499', - name: 'endpoint denial of service', - reference: 'https://attack.mitre.org/techniques/T1499/', - }, - ], - }, - ], - to: 'now', - type: 'query', - }); - }); - - test('should return enabled is equal to false', () => { - const fullRule = getResult(); - fullRule.enabled = false; - const ruleWithEnabledFalse = transformAlertToRule(fullRule); - expect(ruleWithEnabledFalse).toEqual({ - created_by: 'elastic', - description: 'Detecting root and admin users', - enabled: false, - from: 'now-6m', - false_positives: [], - id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', - immutable: false, - output_index: '.siem-signals', - index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], - interval: '5m', - language: 'kuery', - risk_score: 50, - rule_id: 'rule-1', - max_signals: 100, - name: 'Detect Root/Admin Users', - query: 'user.name: root or user.name: admin', - references: ['http://www.example.com', 'https://ww.example.com'], - severity: 'high', - updated_by: 'elastic', - tags: [], - threats: [ - { - framework: 'MITRE ATT&CK', - tactic: { - id: 'TA0040', - name: 'impact', - reference: 'https://attack.mitre.org/tactics/TA0040/', - }, - techniques: [ - { - id: 'T1499', - name: 'endpoint denial of service', - reference: 'https://attack.mitre.org/techniques/T1499/', - }, - ], - }, - ], - to: 'now', - type: 'query', - }); - }); - - test('should return immutable is equal to false', () => { - const fullRule = getResult(); - fullRule.params.immutable = false; - const ruleWithEnabledFalse = transformAlertToRule(fullRule); - expect(ruleWithEnabledFalse).toEqual({ - created_by: 'elastic', - description: 'Detecting root and admin users', - enabled: true, - from: 'now-6m', - false_positives: [], - id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', - immutable: false, - output_index: '.siem-signals', - index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], - interval: '5m', - language: 'kuery', - risk_score: 50, - rule_id: 'rule-1', - max_signals: 100, - name: 'Detect Root/Admin Users', - query: 'user.name: root or user.name: admin', - references: ['http://www.example.com', 'https://ww.example.com'], - severity: 'high', - updated_by: 'elastic', - tags: [], - threats: [ - { - framework: 'MITRE ATT&CK', - tactic: { - id: 'TA0040', - name: 'impact', - reference: 'https://attack.mitre.org/tactics/TA0040/', - }, - techniques: [ - { - id: 'T1499', - name: 'endpoint denial of service', - reference: 'https://attack.mitre.org/techniques/T1499/', - }, - ], - }, - ], - to: 'now', - type: 'query', - }); - }); - }); - - describe('getIdError', () => { - test('outputs message about id not being found if only id is defined and ruleId is undefined', () => { - const boom = getIdError({ id: '123', ruleId: undefined }); - expect(boom.message).toEqual('id: "123" not found'); - }); - - test('outputs message about id not being found if only id is defined and ruleId is null', () => { - const boom = getIdError({ id: '123', ruleId: null }); - expect(boom.message).toEqual('id: "123" not found'); - }); - - test('outputs message about ruleId not being found if only ruleId is defined and id is undefined', () => { - const boom = getIdError({ id: undefined, ruleId: 'rule-id-123' }); - expect(boom.message).toEqual('rule_id: "rule-id-123" not found'); - }); - - test('outputs message about ruleId not being found if only ruleId is defined and id is null', () => { - const boom = getIdError({ id: null, ruleId: 'rule-id-123' }); - expect(boom.message).toEqual('rule_id: "rule-id-123" not found'); - }); - - test('outputs message about both being not defined when both are undefined', () => { - const boom = getIdError({ id: undefined, ruleId: undefined }); - expect(boom.message).toEqual('id or rule_id should have been defined'); - }); - - test('outputs message about both being not defined when both are null', () => { - const boom = getIdError({ id: null, ruleId: null }); - expect(boom.message).toEqual('id or rule_id should have been defined'); - }); - - test('outputs message about both being not defined when id is null and ruleId is undefined', () => { - const boom = getIdError({ id: null, ruleId: undefined }); - expect(boom.message).toEqual('id or rule_id should have been defined'); - }); - - test('outputs message about both being not defined when id is undefined and ruleId is null', () => { - const boom = getIdError({ id: undefined, ruleId: null }); - expect(boom.message).toEqual('id or rule_id should have been defined'); - }); - }); - - describe('transformFindAlertsOrError', () => { - test('outputs empty data set when data set is empty correct', () => { - const output = transformFindAlertsOrError({ data: [] }); - expect(output).toEqual({ data: [] }); - }); - - test('outputs 200 if the data is of type siem alert', () => { - const output = transformFindAlertsOrError({ - data: [getResult()], - }); - expect(output).toEqual({ - data: [ - { - created_by: 'elastic', - description: 'Detecting root and admin users', - enabled: true, - false_positives: [], - from: 'now-6m', - id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', - immutable: false, - output_index: '.siem-signals', - index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], - interval: '5m', - risk_score: 50, - rule_id: 'rule-1', - language: 'kuery', - max_signals: 100, - name: 'Detect Root/Admin Users', - query: 'user.name: root or user.name: admin', - references: ['http://www.example.com', 'https://ww.example.com'], - severity: 'high', - updated_by: 'elastic', - tags: [], - to: 'now', - type: 'query', - threats: [ - { - framework: 'MITRE ATT&CK', - tactic: { - id: 'TA0040', - name: 'impact', - reference: 'https://attack.mitre.org/tactics/TA0040/', - }, - techniques: [ - { - id: 'T1499', - name: 'endpoint denial of service', - reference: 'https://attack.mitre.org/techniques/T1499/', - }, - ], - }, - ], - }, - ], - }); - }); - - test('returns 500 if the data is not of type siem alert', () => { - const output = transformFindAlertsOrError({ data: [{ random: 1 }] }); - expect((output as Boom).message).toEqual('Internal error transforming'); - }); - }); - - describe('transformOrError', () => { - test('outputs 200 if the data is of type siem alert', () => { - const output = transformOrError(getResult()); - expect(output).toEqual({ - created_by: 'elastic', - description: 'Detecting root and admin users', - enabled: true, - false_positives: [], - from: 'now-6m', - id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', - immutable: false, - output_index: '.siem-signals', - index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], - interval: '5m', - rule_id: 'rule-1', - risk_score: 50, - language: 'kuery', - max_signals: 100, - name: 'Detect Root/Admin Users', - query: 'user.name: root or user.name: admin', - references: ['http://www.example.com', 'https://ww.example.com'], - severity: 'high', - updated_by: 'elastic', - tags: [], - to: 'now', - type: 'query', - threats: [ - { - framework: 'MITRE ATT&CK', - tactic: { - id: 'TA0040', - name: 'impact', - reference: 'https://attack.mitre.org/tactics/TA0040/', - }, - techniques: [ - { - id: 'T1499', - name: 'endpoint denial of service', - reference: 'https://attack.mitre.org/techniques/T1499/', - }, - ], - }, - ], - }); - }); - - test('returns 500 if the data is not of type siem alert', () => { - const output = transformOrError({ data: [{ random: 1 }] }); - expect((output as Boom).message).toEqual('Internal error transforming'); - }); - }); - describe('transformError', () => { test('returns boom if it is a boom object', () => { const boom = new Boom(''); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.ts index 6df4174e628b..aed0ced5cdeb 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.ts @@ -5,77 +5,9 @@ */ import Boom from 'boom'; -import { pickBy } from 'lodash/fp'; import { APP_ID, SIGNALS_INDEX_KEY } from '../../../../common/constants'; -import { RuleAlertType, isAlertType, OutputRuleAlertRest, isAlertTypes } from '../alerts/types'; import { ServerFacade, RequestFacade } from '../../../types'; -export const getIdError = ({ - id, - ruleId, -}: { - id: string | undefined | null; - ruleId: string | undefined | null; -}) => { - if (id != null) { - return new Boom(`id: "${id}" not found`, { statusCode: 404 }); - } else if (ruleId != null) { - return new Boom(`rule_id: "${ruleId}" not found`, { statusCode: 404 }); - } else { - return new Boom(`id or rule_id should have been defined`, { statusCode: 404 }); - } -}; - -// Transforms the data but will remove any null or undefined it encounters and not include -// those on the export -export const transformAlertToRule = (alert: RuleAlertType): Partial => { - return pickBy((value: unknown) => value != null, { - created_by: alert.createdBy, - description: alert.params.description, - enabled: alert.enabled, - false_positives: alert.params.falsePositives, - filters: alert.params.filters, - from: alert.params.from, - id: alert.id, - immutable: alert.params.immutable, - index: alert.params.index, - interval: alert.interval, - rule_id: alert.params.ruleId, - language: alert.params.language, - output_index: alert.params.outputIndex, - max_signals: alert.params.maxSignals, - risk_score: alert.params.riskScore, - name: alert.name, - query: alert.params.query, - references: alert.params.references, - saved_id: alert.params.savedId, - meta: alert.params.meta, - severity: alert.params.severity, - updated_by: alert.updatedBy, - tags: alert.tags, - to: alert.params.to, - type: alert.params.type, - threats: alert.params.threats, - }); -}; - -export const transformFindAlertsOrError = (findResults: { data: unknown[] }): unknown | Boom => { - if (isAlertTypes(findResults.data)) { - findResults.data = findResults.data.map(alert => transformAlertToRule(alert)); - return findResults; - } else { - return new Boom('Internal error transforming', { statusCode: 500 }); - } -}; - -export const transformOrError = (alert: unknown): Partial | Boom => { - if (isAlertType(alert)) { - return transformAlertToRule(alert); - } else { - return new Boom('Internal error transforming', { statusCode: 500 }); - } -}; - export const transformError = (err: Error & { statusCode?: number }) => { if (Boom.isBoom(err)) { return err; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/create_rules.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/create_rules.ts similarity index 100% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/create_rules.ts rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/create_rules.ts diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/delete_rules.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/delete_rules.ts similarity index 100% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/delete_rules.ts rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/delete_rules.ts diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/find_rules.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/find_rules.test.ts similarity index 100% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/find_rules.test.ts rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/find_rules.test.ts diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/find_rules.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/find_rules.ts similarity index 100% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/find_rules.ts rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/find_rules.ts diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/read_rules.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/read_rules.test.ts similarity index 100% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/read_rules.test.ts rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/read_rules.test.ts diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/read_rules.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/read_rules.ts similarity index 100% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/read_rules.ts rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/read_rules.ts diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/types.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/types.ts new file mode 100644 index 000000000000..5c0fa76b5262 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/types.ts @@ -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 { get } from 'lodash/fp'; + +import { SIGNALS_ID } from '../../../../common/constants'; +import { AlertsClient } from '../../../../../alerting/server/alerts_client'; +import { ActionsClient } from '../../../../../actions/server/actions_client'; +import { RuleAlertParams, RuleTypeParams, RuleAlertParamsRest } from '../types'; +import { RequestFacade } from '../../../types'; +import { Alert } from '../../../../../alerting/server/types'; + +export type UpdateRuleAlertParamsRest = Partial & { + id: string | undefined; + rule_id: RuleAlertParams['ruleId'] | undefined; +}; + +export interface FindParamsRest { + per_page: number; + page: number; + sort_field: string; + sort_order: 'asc' | 'desc'; + fields: string[]; + filter: string; +} + +export interface UpdateRulesRequest extends RequestFacade { + payload: UpdateRuleAlertParamsRest; +} + +export type RuleAlertType = Alert & { + id: string; + params: RuleTypeParams; +}; + +export interface RulesRequest extends RequestFacade { + payload: RuleAlertParamsRest; +} + +export interface FindRuleParams { + alertsClient: AlertsClient; + perPage?: number; + page?: number; + sortField?: string; + filter?: string; + fields?: string[]; + sortOrder?: 'asc' | 'desc'; +} + +export interface FindRulesRequest extends Omit { + query: { + per_page: number; + page: number; + search?: string; + sort_field?: string; + filter?: string; + fields?: string[]; + sort_order?: 'asc' | 'desc'; + }; +} + +export interface Clients { + alertsClient: AlertsClient; + actionsClient: ActionsClient; +} + +export type UpdateRuleParams = Partial & { + id: string | undefined | null; +} & Clients; + +export type DeleteRuleParams = Clients & { + id: string | undefined; + ruleId: string | undefined | null; +}; + +export type RuleParams = RuleAlertParams & Clients; + +export interface ReadRuleParams { + alertsClient: AlertsClient; + id?: string | undefined | null; + ruleId?: string | undefined | null; +} + +export interface ReadRuleByRuleId { + alertsClient: AlertsClient; + ruleId: string; +} + +export const isAlertTypes = (obj: unknown[]): obj is RuleAlertType[] => { + return obj.every(rule => isAlertType(rule)); +}; + +export const isAlertType = (obj: unknown): obj is RuleAlertType => { + return get('alertTypeId', obj) === SIGNALS_ID; +}; + +export const isAlertTypeArray = (objArray: unknown[]): objArray is RuleAlertType[] => { + return objArray.length === 0 || isAlertType(objArray[0]); +}; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/update_rules.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_rules.test.ts similarity index 100% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/update_rules.test.ts rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_rules.test.ts diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/update_rules.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_rules.ts similarity index 100% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/update_rules.ts rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_rules.ts diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/__mocks__/es_results.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/__mocks__/es_results.ts similarity index 94% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/__mocks__/es_results.ts rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/__mocks__/es_results.ts index 4c113544e6e2..215d9da6eb7f 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/__mocks__/es_results.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/__mocks__/es_results.ts @@ -4,12 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - SignalSourceHit, - SignalSearchResponse, - RuleTypeParams, - OutputRuleAlertRest, -} from '../types'; +import { SignalSourceHit, SignalSearchResponse } from '../types'; +import { Logger } from 'kibana/server'; +import { RuleTypeParams, OutputRuleAlertRest } from '../../types'; export const sampleRuleAlertParams = ( maxSignals?: number | undefined, @@ -281,3 +278,13 @@ export const sampleRule = (): Partial => { type: 'query', }; }; + +export const mockLogger: Logger = { + log: jest.fn(), + trace: jest.fn(), + debug: jest.fn(), + info: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + fatal: jest.fn(), +}; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_bulk_body.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_bulk_body.test.ts new file mode 100644 index 000000000000..e10158a0b879 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_bulk_body.test.ts @@ -0,0 +1,284 @@ +/* + * 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 { + sampleRuleAlertParams, + sampleDocNoSortId, + sampleRuleGuid, + sampleIdGuid, +} from './__mocks__/es_results'; +import { buildBulkBody } from './build_bulk_body'; + +describe('buildBulkBody', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + test('if bulk body builds well-defined body', () => { + const sampleParams = sampleRuleAlertParams(); + const fakeSignalSourceHit = buildBulkBody({ + doc: sampleDocNoSortId(), + ruleParams: sampleParams, + id: sampleRuleGuid, + name: 'rule-name', + createdBy: 'elastic', + updatedBy: 'elastic', + interval: '5m', + enabled: true, + tags: ['some fake tag 1', 'some fake tag 2'], + }); + // Timestamp will potentially always be different so remove it for the test + delete fakeSignalSourceHit['@timestamp']; + expect(fakeSignalSourceHit).toEqual({ + someKey: 'someValue', + event: { + kind: 'signal', + }, + signal: { + parent: { + id: sampleIdGuid, + type: 'event', + index: 'myFakeSignalIndex', + depth: 1, + }, + original_time: 'someTimeStamp', + status: 'open', + rule: { + id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + rule_id: 'rule-1', + false_positives: [], + max_signals: 10000, + risk_score: 50, + output_index: '.siem-signals', + description: 'Detecting root and admin users', + from: 'now-6m', + immutable: false, + index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + interval: '5m', + language: 'kuery', + name: 'rule-name', + query: 'user.name: root or user.name: admin', + references: ['http://google.com'], + severity: 'high', + tags: ['some fake tag 1', 'some fake tag 2'], + type: 'query', + to: 'now', + enabled: true, + created_by: 'elastic', + updated_by: 'elastic', + }, + }, + }); + }); + + test('if bulk body builds original_event if it exists on the event to begin with', () => { + const sampleParams = sampleRuleAlertParams(); + const doc = sampleDocNoSortId(); + doc._source.event = { + action: 'socket_opened', + module: 'system', + dataset: 'socket', + kind: 'event', + }; + const fakeSignalSourceHit = buildBulkBody({ + doc, + ruleParams: sampleParams, + id: sampleRuleGuid, + name: 'rule-name', + createdBy: 'elastic', + updatedBy: 'elastic', + interval: '5m', + enabled: true, + tags: ['some fake tag 1', 'some fake tag 2'], + }); + // Timestamp will potentially always be different so remove it for the test + delete fakeSignalSourceHit['@timestamp']; + expect(fakeSignalSourceHit).toEqual({ + someKey: 'someValue', + event: { + action: 'socket_opened', + dataset: 'socket', + kind: 'signal', + module: 'system', + }, + signal: { + original_event: { + action: 'socket_opened', + dataset: 'socket', + kind: 'event', + module: 'system', + }, + parent: { + id: sampleIdGuid, + type: 'event', + index: 'myFakeSignalIndex', + depth: 1, + }, + original_time: 'someTimeStamp', + status: 'open', + rule: { + id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + rule_id: 'rule-1', + false_positives: [], + max_signals: 10000, + risk_score: 50, + output_index: '.siem-signals', + description: 'Detecting root and admin users', + from: 'now-6m', + immutable: false, + index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + interval: '5m', + language: 'kuery', + name: 'rule-name', + query: 'user.name: root or user.name: admin', + references: ['http://google.com'], + severity: 'high', + tags: ['some fake tag 1', 'some fake tag 2'], + type: 'query', + to: 'now', + enabled: true, + created_by: 'elastic', + updated_by: 'elastic', + }, + }, + }); + }); + + test('if bulk body builds original_event if it exists on the event to begin with but no kind information', () => { + const sampleParams = sampleRuleAlertParams(); + const doc = sampleDocNoSortId(); + doc._source.event = { + action: 'socket_opened', + module: 'system', + dataset: 'socket', + }; + const fakeSignalSourceHit = buildBulkBody({ + doc, + ruleParams: sampleParams, + id: sampleRuleGuid, + name: 'rule-name', + createdBy: 'elastic', + updatedBy: 'elastic', + interval: '5m', + enabled: true, + tags: ['some fake tag 1', 'some fake tag 2'], + }); + // Timestamp will potentially always be different so remove it for the test + delete fakeSignalSourceHit['@timestamp']; + expect(fakeSignalSourceHit).toEqual({ + someKey: 'someValue', + event: { + action: 'socket_opened', + dataset: 'socket', + kind: 'signal', + module: 'system', + }, + signal: { + original_event: { + action: 'socket_opened', + dataset: 'socket', + module: 'system', + }, + parent: { + id: sampleIdGuid, + type: 'event', + index: 'myFakeSignalIndex', + depth: 1, + }, + original_time: 'someTimeStamp', + status: 'open', + rule: { + id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + rule_id: 'rule-1', + false_positives: [], + max_signals: 10000, + risk_score: 50, + output_index: '.siem-signals', + description: 'Detecting root and admin users', + from: 'now-6m', + immutable: false, + index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + interval: '5m', + language: 'kuery', + name: 'rule-name', + query: 'user.name: root or user.name: admin', + references: ['http://google.com'], + severity: 'high', + tags: ['some fake tag 1', 'some fake tag 2'], + type: 'query', + to: 'now', + enabled: true, + created_by: 'elastic', + updated_by: 'elastic', + }, + }, + }); + }); + + test('if bulk body builds original_event if it exists on the event to begin with with only kind information', () => { + const sampleParams = sampleRuleAlertParams(); + const doc = sampleDocNoSortId(); + doc._source.event = { + kind: 'event', + }; + const fakeSignalSourceHit = buildBulkBody({ + doc, + ruleParams: sampleParams, + id: sampleRuleGuid, + name: 'rule-name', + createdBy: 'elastic', + updatedBy: 'elastic', + interval: '5m', + enabled: true, + tags: ['some fake tag 1', 'some fake tag 2'], + }); + // Timestamp will potentially always be different so remove it for the test + delete fakeSignalSourceHit['@timestamp']; + expect(fakeSignalSourceHit).toEqual({ + someKey: 'someValue', + event: { + kind: 'signal', + }, + signal: { + original_event: { + kind: 'event', + }, + parent: { + id: sampleIdGuid, + type: 'event', + index: 'myFakeSignalIndex', + depth: 1, + }, + original_time: 'someTimeStamp', + status: 'open', + rule: { + id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + rule_id: 'rule-1', + false_positives: [], + max_signals: 10000, + risk_score: 50, + output_index: '.siem-signals', + description: 'Detecting root and admin users', + from: 'now-6m', + immutable: false, + index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + interval: '5m', + language: 'kuery', + name: 'rule-name', + query: 'user.name: root or user.name: admin', + references: ['http://google.com'], + severity: 'high', + tags: ['some fake tag 1', 'some fake tag 2'], + type: 'query', + to: 'now', + enabled: true, + created_by: 'elastic', + updated_by: 'elastic', + }, + }, + }); + }); +}); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_bulk_body.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_bulk_body.ts new file mode 100644 index 000000000000..6d9f442515b2 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_bulk_body.ts @@ -0,0 +1,56 @@ +/* + * 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 { SignalSourceHit, SignalHit } from './types'; +import { buildRule } from './build_rule'; +import { buildSignal } from './build_signal'; +import { buildEventTypeSignal } from './build_event_type_signal'; +import { RuleTypeParams } from '../types'; + +interface BuildBulkBodyParams { + doc: SignalSourceHit; + ruleParams: RuleTypeParams; + id: string; + name: string; + createdBy: string; + updatedBy: string; + interval: string; + enabled: boolean; + tags: string[]; +} + +// format search_after result for signals index. +export const buildBulkBody = ({ + doc, + ruleParams, + id, + name, + createdBy, + updatedBy, + interval, + enabled, + tags, +}: BuildBulkBodyParams): SignalHit => { + const rule = buildRule({ + ruleParams, + id, + name, + enabled, + createdBy, + updatedBy, + interval, + tags, + }); + const signal = buildSignal(doc, rule); + const event = buildEventTypeSignal(doc); + const signalHit: SignalHit = { + ...doc._source, + '@timestamp': new Date().toISOString(), + event, + signal, + }; + return signalHit; +}; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_event_type_signal.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_event_type_signal.test.ts new file mode 100644 index 000000000000..106a049002e0 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_event_type_signal.test.ts @@ -0,0 +1,47 @@ +/* + * 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 { sampleDocNoSortId } from './__mocks__/es_results'; +import { buildEventTypeSignal } from './build_event_type_signal'; + +describe('buildEventTypeSignal', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + test('it returns the event appended of kind signal if it does not exist', () => { + const doc = sampleDocNoSortId(); + delete doc._source.event; + const eventType = buildEventTypeSignal(doc); + const expected: object = { kind: 'signal' }; + expect(eventType).toEqual(expected); + }); + + test('it returns the event appended of kind signal if it is an empty object', () => { + const doc = sampleDocNoSortId(); + doc._source.event = {}; + const eventType = buildEventTypeSignal(doc); + const expected: object = { kind: 'signal' }; + expect(eventType).toEqual(expected); + }); + + test('it returns the event with kind signal and other properties if they exist', () => { + const doc = sampleDocNoSortId(); + doc._source.event = { + action: 'socket_opened', + module: 'system', + dataset: 'socket', + }; + const eventType = buildEventTypeSignal(doc); + const expected: object = { + action: 'socket_opened', + module: 'system', + dataset: 'socket', + kind: 'signal', + }; + expect(eventType).toEqual(expected); + }); +}); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_event_type_signal.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_event_type_signal.ts new file mode 100644 index 000000000000..59cdc020c611 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_event_type_signal.ts @@ -0,0 +1,15 @@ +/* + * 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 { SignalSourceHit } from './types'; + +export const buildEventTypeSignal = (doc: SignalSourceHit): object => { + if (doc._source.event != null && doc._source.event instanceof Object) { + return { ...doc._source.event, kind: 'signal' }; + } else { + return { kind: 'signal' }; + } +}; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/build_events_query.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_events_query.test.ts similarity index 100% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/build_events_query.test.ts rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_events_query.test.ts diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/build_events_query.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_events_query.ts similarity index 100% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/build_events_query.ts rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_events_query.ts diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_rule.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_rule.test.ts new file mode 100644 index 000000000000..c12c6fd333f5 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_rule.test.ts @@ -0,0 +1,156 @@ +/* + * 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 { buildRule } from './build_rule'; +import { sampleRuleAlertParams, sampleRuleGuid } from './__mocks__/es_results'; +import { OutputRuleAlertRest } from '../types'; + +describe('buildRule', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + test('it builds a rule as expected with filters present', () => { + const ruleParams = sampleRuleAlertParams(); + ruleParams.filters = [ + { + query: 'host.name: Rebecca', + }, + { + query: 'host.name: Evan', + }, + { + query: 'host.name: Braden', + }, + ]; + const rule = buildRule({ + ruleParams, + name: 'some-name', + id: sampleRuleGuid, + enabled: false, + createdBy: 'elastic', + updatedBy: 'elastic', + interval: 'some interval', + tags: ['some fake tag 1', 'some fake tag 2'], + }); + const expected: Partial = { + created_by: 'elastic', + description: 'Detecting root and admin users', + enabled: false, + false_positives: [], + from: 'now-6m', + id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + immutable: false, + index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + interval: 'some interval', + language: 'kuery', + max_signals: 10000, + name: 'some-name', + output_index: '.siem-signals', + query: 'user.name: root or user.name: admin', + references: ['http://google.com'], + risk_score: 50, + rule_id: 'rule-1', + severity: 'high', + tags: ['some fake tag 1', 'some fake tag 2'], + to: 'now', + type: 'query', + updated_by: 'elastic', + filters: [ + { + query: 'host.name: Rebecca', + }, + { + query: 'host.name: Evan', + }, + { + query: 'host.name: Braden', + }, + ], + }; + expect(rule).toEqual(expected); + }); + + test('it omits a null value such as if enabled is null if is present', () => { + const ruleParams = sampleRuleAlertParams(); + ruleParams.filters = undefined; + const rule = buildRule({ + ruleParams, + name: 'some-name', + id: sampleRuleGuid, + enabled: true, + createdBy: 'elastic', + updatedBy: 'elastic', + interval: 'some interval', + tags: ['some fake tag 1', 'some fake tag 2'], + }); + const expected: Partial = { + created_by: 'elastic', + description: 'Detecting root and admin users', + enabled: true, + false_positives: [], + from: 'now-6m', + id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + immutable: false, + index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + interval: 'some interval', + language: 'kuery', + max_signals: 10000, + name: 'some-name', + output_index: '.siem-signals', + query: 'user.name: root or user.name: admin', + references: ['http://google.com'], + risk_score: 50, + rule_id: 'rule-1', + severity: 'high', + tags: ['some fake tag 1', 'some fake tag 2'], + to: 'now', + type: 'query', + updated_by: 'elastic', + }; + expect(rule).toEqual(expected); + }); + + test('it omits a null value such as if filters is undefined if is present', () => { + const ruleParams = sampleRuleAlertParams(); + ruleParams.filters = undefined; + const rule = buildRule({ + ruleParams, + name: 'some-name', + id: sampleRuleGuid, + enabled: true, + createdBy: 'elastic', + updatedBy: 'elastic', + interval: 'some interval', + tags: ['some fake tag 1', 'some fake tag 2'], + }); + const expected: Partial = { + created_by: 'elastic', + description: 'Detecting root and admin users', + enabled: true, + false_positives: [], + from: 'now-6m', + id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + immutable: false, + index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + interval: 'some interval', + language: 'kuery', + max_signals: 10000, + name: 'some-name', + output_index: '.siem-signals', + query: 'user.name: root or user.name: admin', + references: ['http://google.com'], + risk_score: 50, + rule_id: 'rule-1', + severity: 'high', + tags: ['some fake tag 1', 'some fake tag 2'], + to: 'now', + type: 'query', + updated_by: 'elastic', + }; + expect(rule).toEqual(expected); + }); +}); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_rule.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_rule.ts new file mode 100644 index 000000000000..64ec989208b6 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_rule.ts @@ -0,0 +1,59 @@ +/* + * 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 { pickBy } from 'lodash/fp'; +import { RuleTypeParams, OutputRuleAlertRest } from '../types'; + +interface BuildRuleParams { + ruleParams: RuleTypeParams; + name: string; + id: string; + enabled: boolean; + createdBy: string; + updatedBy: string; + interval: string; + tags: string[]; +} + +export const buildRule = ({ + ruleParams, + name, + id, + enabled, + createdBy, + updatedBy, + interval, + tags, +}: BuildRuleParams): Partial => { + return pickBy((value: unknown) => value != null, { + id, + rule_id: ruleParams.ruleId, + false_positives: ruleParams.falsePositives, + saved_id: ruleParams.savedId, + meta: ruleParams.meta, + max_signals: ruleParams.maxSignals, + risk_score: ruleParams.riskScore, + output_index: ruleParams.outputIndex, + description: ruleParams.description, + from: ruleParams.from, + immutable: ruleParams.immutable, + index: ruleParams.index, + interval, + language: ruleParams.language, + name, + query: ruleParams.query, + references: ruleParams.references, + severity: ruleParams.severity, + tags, + type: ruleParams.type, + to: ruleParams.to, + enabled, + filters: ruleParams.filters, + created_by: createdBy, + updated_by: updatedBy, + threats: ruleParams.threats, + }); +}; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_signal.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_signal.test.ts new file mode 100644 index 000000000000..1c024d049674 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_signal.test.ts @@ -0,0 +1,111 @@ +/* + * 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 { sampleDocNoSortId, sampleRule } from './__mocks__/es_results'; +import { buildSignal } from './build_signal'; +import { OutputRuleAlertRest } from '../types'; +import { Signal } from './types'; + +describe('buildSignal', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + test('it builds a signal as expected without original_event if event does not exist', () => { + const doc = sampleDocNoSortId('d5e8eb51-a6a0-456d-8a15-4b79bfec3d71'); + delete doc._source.event; + const rule: Partial = sampleRule(); + const signal = buildSignal(doc, rule); + const expected: Signal = { + parent: { + id: 'd5e8eb51-a6a0-456d-8a15-4b79bfec3d71', + type: 'event', + index: 'myFakeSignalIndex', + depth: 1, + }, + original_time: 'someTimeStamp', + status: 'open', + rule: { + created_by: 'elastic', + description: 'Detecting root and admin users', + enabled: true, + false_positives: [], + from: 'now-6m', + id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + immutable: false, + index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + interval: '5m', + risk_score: 50, + rule_id: 'rule-1', + language: 'kuery', + max_signals: 100, + name: 'Detect Root/Admin Users', + output_index: '.siem-signals', + query: 'user.name: root or user.name: admin', + references: ['http://www.example.com', 'https://ww.example.com'], + severity: 'high', + updated_by: 'elastic', + tags: ['some fake tag 1', 'some fake tag 2'], + to: 'now', + type: 'query', + }, + }; + expect(signal).toEqual(expected); + }); + + test('it builds a signal as expected with original_event if is present', () => { + const doc = sampleDocNoSortId('d5e8eb51-a6a0-456d-8a15-4b79bfec3d71'); + doc._source.event = { + action: 'socket_opened', + dataset: 'socket', + kind: 'event', + module: 'system', + }; + const rule: Partial = sampleRule(); + const signal = buildSignal(doc, rule); + const expected: Signal = { + parent: { + id: 'd5e8eb51-a6a0-456d-8a15-4b79bfec3d71', + type: 'event', + index: 'myFakeSignalIndex', + depth: 1, + }, + original_time: 'someTimeStamp', + original_event: { + action: 'socket_opened', + dataset: 'socket', + kind: 'event', + module: 'system', + }, + status: 'open', + rule: { + created_by: 'elastic', + description: 'Detecting root and admin users', + enabled: true, + false_positives: [], + from: 'now-6m', + id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + immutable: false, + index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + interval: '5m', + risk_score: 50, + rule_id: 'rule-1', + language: 'kuery', + max_signals: 100, + name: 'Detect Root/Admin Users', + output_index: '.siem-signals', + query: 'user.name: root or user.name: admin', + references: ['http://www.example.com', 'https://ww.example.com'], + severity: 'high', + updated_by: 'elastic', + tags: ['some fake tag 1', 'some fake tag 2'], + to: 'now', + type: 'query', + }, + }; + expect(signal).toEqual(expected); + }); +}); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_signal.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_signal.ts new file mode 100644 index 000000000000..4131c843297e --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_signal.ts @@ -0,0 +1,26 @@ +/* + * 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 { SignalSourceHit, Signal } from './types'; +import { OutputRuleAlertRest } from '../types'; + +export const buildSignal = (doc: SignalSourceHit, rule: Partial): Signal => { + const signal: Signal = { + parent: { + id: doc._id, + type: 'event', + index: doc._index, + depth: 1, + }, + original_time: doc._source['@timestamp'], + status: 'open', + rule, + }; + if (doc._source.event != null) { + return { ...signal, original_event: doc._source.event }; + } + return signal; +}; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/get_filter.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/get_filter.test.ts similarity index 99% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/get_filter.test.ts rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/get_filter.test.ts index e1d10e2efdef..43b5ce4b590a 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/get_filter.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/get_filter.test.ts @@ -7,7 +7,7 @@ import { getQueryFilter, getFilter } from './get_filter'; import { savedObjectsClientMock } from 'src/core/server/mocks'; import { AlertServices } from '../../../../../alerting/server/types'; -import { PartialFilter } from './types'; +import { PartialFilter } from '../types'; describe('get_filter', () => { let savedObjectsClient = savedObjectsClientMock.create(); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/get_filter.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/get_filter.ts similarity index 98% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/get_filter.ts rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/get_filter.ts index 858f3580f57e..8a67d0cb5c5b 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/get_filter.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/get_filter.ts @@ -5,7 +5,6 @@ */ import { AlertServices } from '../../../../../alerting/server/types'; -import { RuleAlertParams, PartialFilter } from './types'; import { assertUnreachable } from '../../../utils/build_query'; import { Query, @@ -13,6 +12,7 @@ import { esFilters, IIndexPattern, } from '../../../../../../../../src/plugins/data/server'; +import { PartialFilter, RuleAlertParams } from '../types'; export const getQueryFilter = ( query: string, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/get_input_output_index.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/get_input_output_index.test.ts similarity index 100% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/get_input_output_index.test.ts rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/get_input_output_index.test.ts diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/get_input_output_index.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/get_input_output_index.ts similarity index 100% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/get_input_output_index.ts rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/get_input_output_index.ts diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.test.ts new file mode 100644 index 000000000000..ac6f840943f1 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.test.ts @@ -0,0 +1,286 @@ +/* + * 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 { + sampleRuleAlertParams, + sampleEmptyDocSearchResults, + sampleRuleGuid, + mockLogger, + repeatedSearchResultsWithSortId, + sampleBulkCreateDuplicateResult, + sampleDocSearchResultsNoSortId, + sampleDocSearchResultsNoSortIdNoHits, +} from './__mocks__/es_results'; +import { searchAfterAndBulkCreate } from './search_after_bulk_create'; +import { DEFAULT_SIGNALS_INDEX } from '../../../../common/constants'; +import { savedObjectsClientMock } from 'src/core/server/mocks'; +import uuid from 'uuid'; + +export const mockService = { + callCluster: jest.fn(), + alertInstanceFactory: jest.fn(), + savedObjectsClient: savedObjectsClientMock.create(), +}; + +describe('searchAfterAndBulkCreate', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + test('if successful with empty search results', async () => { + const sampleParams = sampleRuleAlertParams(); + const result = await searchAfterAndBulkCreate({ + someResult: sampleEmptyDocSearchResults, + ruleParams: sampleParams, + services: mockService, + logger: mockLogger, + id: sampleRuleGuid, + signalsIndex: DEFAULT_SIGNALS_INDEX, + name: 'rule-name', + createdBy: 'elastic', + updatedBy: 'elastic', + interval: '5m', + enabled: true, + pageSize: 1, + filter: undefined, + tags: ['some fake tag 1', 'some fake tag 2'], + }); + expect(mockService.callCluster).toHaveBeenCalledTimes(0); + expect(result).toEqual(true); + }); + test('if successful iteration of while loop with maxDocs', async () => { + const sampleParams = sampleRuleAlertParams(30); + const someGuids = Array.from({ length: 13 }).map(x => uuid.v4()); + mockService.callCluster + .mockReturnValueOnce({ + took: 100, + errors: false, + items: [ + { + fakeItemValue: 'fakeItemKey', + }, + ], + }) + .mockReturnValueOnce(repeatedSearchResultsWithSortId(3, 1, someGuids.slice(0, 3))) + .mockReturnValueOnce({ + took: 100, + errors: false, + items: [ + { + fakeItemValue: 'fakeItemKey', + }, + ], + }) + .mockReturnValueOnce(repeatedSearchResultsWithSortId(3, 1, someGuids.slice(3, 6))) + .mockReturnValueOnce({ + took: 100, + errors: false, + items: [ + { + fakeItemValue: 'fakeItemKey', + }, + ], + }); + const result = await searchAfterAndBulkCreate({ + someResult: repeatedSearchResultsWithSortId(3, 1, someGuids.slice(6, 9)), + ruleParams: sampleParams, + services: mockService, + logger: mockLogger, + id: sampleRuleGuid, + signalsIndex: DEFAULT_SIGNALS_INDEX, + name: 'rule-name', + createdBy: 'elastic', + updatedBy: 'elastic', + interval: '5m', + enabled: true, + pageSize: 1, + filter: undefined, + tags: ['some fake tag 1', 'some fake tag 2'], + }); + expect(mockService.callCluster).toHaveBeenCalledTimes(5); + expect(result).toEqual(true); + }); + test('if unsuccessful first bulk create', async () => { + const someGuids = Array.from({ length: 4 }).map(x => uuid.v4()); + const sampleParams = sampleRuleAlertParams(10); + mockService.callCluster.mockReturnValue(sampleBulkCreateDuplicateResult); + const result = await searchAfterAndBulkCreate({ + someResult: repeatedSearchResultsWithSortId(4, 1, someGuids), + ruleParams: sampleParams, + services: mockService, + logger: mockLogger, + id: sampleRuleGuid, + signalsIndex: DEFAULT_SIGNALS_INDEX, + name: 'rule-name', + createdBy: 'elastic', + updatedBy: 'elastic', + interval: '5m', + enabled: true, + pageSize: 1, + filter: undefined, + tags: ['some fake tag 1', 'some fake tag 2'], + }); + expect(mockLogger.error).toHaveBeenCalled(); + expect(result).toEqual(false); + }); + test('if unsuccessful iteration of searchAfterAndBulkCreate due to empty sort ids', async () => { + const sampleParams = sampleRuleAlertParams(); + mockService.callCluster.mockReturnValueOnce({ + took: 100, + errors: false, + items: [ + { + fakeItemValue: 'fakeItemKey', + }, + ], + }); + const result = await searchAfterAndBulkCreate({ + someResult: sampleDocSearchResultsNoSortId(), + ruleParams: sampleParams, + services: mockService, + logger: mockLogger, + id: sampleRuleGuid, + signalsIndex: DEFAULT_SIGNALS_INDEX, + name: 'rule-name', + createdBy: 'elastic', + updatedBy: 'elastic', + interval: '5m', + enabled: true, + pageSize: 1, + filter: undefined, + tags: ['some fake tag 1', 'some fake tag 2'], + }); + expect(mockLogger.error).toHaveBeenCalled(); + expect(result).toEqual(false); + }); + test('if unsuccessful iteration of searchAfterAndBulkCreate due to empty sort ids and 0 total hits', async () => { + const sampleParams = sampleRuleAlertParams(); + mockService.callCluster.mockReturnValueOnce({ + took: 100, + errors: false, + items: [ + { + fakeItemValue: 'fakeItemKey', + }, + ], + }); + const result = await searchAfterAndBulkCreate({ + someResult: sampleDocSearchResultsNoSortIdNoHits(), + ruleParams: sampleParams, + services: mockService, + logger: mockLogger, + id: sampleRuleGuid, + signalsIndex: DEFAULT_SIGNALS_INDEX, + name: 'rule-name', + createdBy: 'elastic', + updatedBy: 'elastic', + interval: '5m', + enabled: true, + pageSize: 1, + filter: undefined, + tags: ['some fake tag 1', 'some fake tag 2'], + }); + expect(result).toEqual(true); + }); + test('if successful iteration of while loop with maxDocs and search after returns results with no sort ids', async () => { + const sampleParams = sampleRuleAlertParams(10); + const someGuids = Array.from({ length: 4 }).map(x => uuid.v4()); + mockService.callCluster + .mockReturnValueOnce({ + took: 100, + errors: false, + items: [ + { + fakeItemValue: 'fakeItemKey', + }, + ], + }) + .mockReturnValueOnce(sampleDocSearchResultsNoSortId()); + const result = await searchAfterAndBulkCreate({ + someResult: repeatedSearchResultsWithSortId(4, 1, someGuids), + ruleParams: sampleParams, + services: mockService, + logger: mockLogger, + id: sampleRuleGuid, + signalsIndex: DEFAULT_SIGNALS_INDEX, + name: 'rule-name', + createdBy: 'elastic', + updatedBy: 'elastic', + interval: '5m', + enabled: true, + pageSize: 1, + filter: undefined, + tags: ['some fake tag 1', 'some fake tag 2'], + }); + expect(result).toEqual(true); + }); + test('if successful iteration of while loop with maxDocs and search after returns empty results with no sort ids', async () => { + const sampleParams = sampleRuleAlertParams(10); + const someGuids = Array.from({ length: 4 }).map(x => uuid.v4()); + mockService.callCluster + .mockReturnValueOnce({ + took: 100, + errors: false, + items: [ + { + fakeItemValue: 'fakeItemKey', + }, + ], + }) + .mockReturnValueOnce(sampleEmptyDocSearchResults); + const result = await searchAfterAndBulkCreate({ + someResult: repeatedSearchResultsWithSortId(4, 1, someGuids), + ruleParams: sampleParams, + services: mockService, + logger: mockLogger, + id: sampleRuleGuid, + signalsIndex: DEFAULT_SIGNALS_INDEX, + name: 'rule-name', + createdBy: 'elastic', + updatedBy: 'elastic', + interval: '5m', + enabled: true, + pageSize: 1, + filter: undefined, + tags: ['some fake tag 1', 'some fake tag 2'], + }); + expect(result).toEqual(true); + }); + test('if returns false when singleSearchAfter throws an exception', async () => { + const sampleParams = sampleRuleAlertParams(10); + const someGuids = Array.from({ length: 4 }).map(x => uuid.v4()); + mockService.callCluster + .mockReturnValueOnce({ + took: 100, + errors: false, + items: [ + { + fakeItemValue: 'fakeItemKey', + }, + ], + }) + .mockImplementation(() => { + throw Error('Fake Error'); + }); + const result = await searchAfterAndBulkCreate({ + someResult: repeatedSearchResultsWithSortId(4, 1, someGuids), + ruleParams: sampleParams, + services: mockService, + logger: mockLogger, + id: sampleRuleGuid, + signalsIndex: DEFAULT_SIGNALS_INDEX, + name: 'rule-name', + createdBy: 'elastic', + updatedBy: 'elastic', + interval: '5m', + enabled: true, + pageSize: 1, + filter: undefined, + tags: ['some fake tag 1', 'some fake tag 2'], + }); + expect(result).toEqual(false); + }); +}); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.ts new file mode 100644 index 000000000000..fb314e62ba94 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.ts @@ -0,0 +1,135 @@ +/* + * 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 { RuleTypeParams } from '../types'; +import { AlertServices } from '../../../../../alerting/server/types'; +import { Logger } from '../../../../../../../../src/core/server'; +import { singleSearchAfter } from './single_search_after'; +import { singleBulkCreate } from './single_bulk_create'; +import { SignalSearchResponse } from './types'; + +interface SearchAfterAndBulkCreateParams { + someResult: SignalSearchResponse; + ruleParams: RuleTypeParams; + services: AlertServices; + logger: Logger; + id: string; + signalsIndex: string; + name: string; + createdBy: string; + updatedBy: string; + interval: string; + enabled: boolean; + pageSize: number; + filter: unknown; + tags: string[]; +} + +// search_after through documents and re-index using bulk endpoint. +export const searchAfterAndBulkCreate = async ({ + someResult, + ruleParams, + services, + logger, + id, + signalsIndex, + filter, + name, + createdBy, + updatedBy, + interval, + enabled, + pageSize, + tags, +}: SearchAfterAndBulkCreateParams): Promise => { + if (someResult.hits.hits.length === 0) { + return true; + } + + logger.debug('[+] starting bulk insertion'); + await singleBulkCreate({ + someResult, + ruleParams, + services, + logger, + id, + signalsIndex, + name, + createdBy, + updatedBy, + interval, + enabled, + tags, + }); + const totalHits = + typeof someResult.hits.total === 'number' ? someResult.hits.total : someResult.hits.total.value; + // maxTotalHitsSize represents the total number of docs to + // query for, no matter the size of each individual page of search results. + // If the total number of hits for the overall search result is greater than + // maxSignals, default to requesting a total of maxSignals, otherwise use the + // totalHits in the response from the searchAfter query. + const maxTotalHitsSize = totalHits >= ruleParams.maxSignals ? ruleParams.maxSignals : totalHits; + + // number of docs in the current search result + let hitsSize = someResult.hits.hits.length; + logger.debug(`first size: ${hitsSize}`); + let sortIds = someResult.hits.hits[0].sort; + if (sortIds == null && totalHits > 0) { + logger.error('sortIds was empty on first search but expected more'); + return false; + } else if (sortIds == null && totalHits === 0) { + return true; + } + let sortId; + if (sortIds != null) { + sortId = sortIds[0]; + } + while (hitsSize < maxTotalHitsSize && hitsSize !== 0) { + try { + logger.debug(`sortIds: ${sortIds}`); + const searchAfterResult: SignalSearchResponse = await singleSearchAfter({ + searchAfterSortId: sortId, + ruleParams, + services, + logger, + filter, + pageSize, // maximum number of docs to receive per search result. + }); + if (searchAfterResult.hits.hits.length === 0) { + return true; + } + hitsSize += searchAfterResult.hits.hits.length; + logger.debug(`size adjusted: ${hitsSize}`); + sortIds = searchAfterResult.hits.hits[0].sort; + if (sortIds == null) { + logger.debug('sortIds was empty on search'); + return true; // no more search results + } + sortId = sortIds[0]; + logger.debug('next bulk index'); + await singleBulkCreate({ + someResult: searchAfterResult, + ruleParams, + services, + logger, + id, + signalsIndex, + name, + createdBy, + updatedBy, + interval, + enabled, + tags, + }); + logger.debug('finished next bulk index'); + } catch (exc) { + logger.error(`[-] search_after and bulk threw an error ${exc}`); + return false; + } + } + logger.debug(`[+] completed bulk index of ${maxTotalHitsSize}`); + return true; +}; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/rules_alert_type.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts similarity index 96% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/rules_alert_type.ts rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts index 9823d8b3b9be..37467e405dd8 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/rules_alert_type.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts @@ -13,18 +13,18 @@ import { } from '../../../../common/constants'; import { buildEventsSearchQuery } from './build_events_query'; -import { searchAfterAndBulkCreate } from './utils'; -import { RuleAlertTypeDefinition } from './types'; -import { getFilter } from './get_filter'; import { getInputIndex } from './get_input_output_index'; +import { searchAfterAndBulkCreate } from './search_after_bulk_create'; +import { getFilter } from './get_filter'; +import { SignalRuleAlertTypeDefinition } from './types'; -export const rulesAlertType = ({ +export const signalRulesAlertType = ({ logger, version, }: { logger: Logger; version: string; -}): RuleAlertTypeDefinition => { +}): SignalRuleAlertTypeDefinition => { return { id: SIGNALS_ID, name: 'SIEM Signals', diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.test.ts new file mode 100644 index 000000000000..d58f0a22b763 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.test.ts @@ -0,0 +1,230 @@ +/* + * 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 { generateId } from './utils'; +import { + sampleRuleAlertParams, + sampleDocSearchResultsNoSortId, + mockLogger, + sampleRuleGuid, + sampleDocSearchResultsNoSortIdNoVersion, + sampleEmptyDocSearchResults, + sampleBulkCreateDuplicateResult, +} from './__mocks__/es_results'; +import { savedObjectsClientMock } from 'src/core/server/mocks'; +import { DEFAULT_SIGNALS_INDEX } from '../../../../common/constants'; +import { singleBulkCreate } from './single_bulk_create'; + +export const mockService = { + callCluster: jest.fn(), + alertInstanceFactory: jest.fn(), + savedObjectsClient: savedObjectsClientMock.create(), +}; + +describe('singleBulkCreate', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('create signal id gereateId', () => { + test('two docs with same index, id, and version should have same id', () => { + const findex = 'myfakeindex'; + const fid = 'somefakeid'; + const version = '1'; + const ruleId = 'rule-1'; + // 'myfakeindexsomefakeid1rule-1' + const generatedHash = '342404d620be4344d6d90dd0461d1d1848aec457944d5c5f40cc0cbfedb36679'; + const firstHash = generateId(findex, fid, version, ruleId); + const secondHash = generateId(findex, fid, version, ruleId); + expect(firstHash).toEqual(generatedHash); + expect(secondHash).toEqual(generatedHash); + expect(Buffer.byteLength(firstHash, 'utf8')).toBeLessThan(512); // 512 bytes is maximum size of _id field + expect(Buffer.byteLength(secondHash, 'utf8')).toBeLessThan(512); + }); + test('two docs with different index, id, and version should have different id', () => { + const findex = 'myfakeindex'; + const findex2 = 'mysecondfakeindex'; + const fid = 'somefakeid'; + const version = '1'; + const ruleId = 'rule-1'; + // 'myfakeindexsomefakeid1rule-1' + const firstGeneratedHash = '342404d620be4344d6d90dd0461d1d1848aec457944d5c5f40cc0cbfedb36679'; + // 'mysecondfakeindexsomefakeid1rule-1' + const secondGeneratedHash = + 'a852941273f805ffe9006e574601acc8ae1148d6c0b3f7f8c4785cba8f6b768a'; + const firstHash = generateId(findex, fid, version, ruleId); + const secondHash = generateId(findex2, fid, version, ruleId); + expect(firstHash).toEqual(firstGeneratedHash); + expect(secondHash).toEqual(secondGeneratedHash); + expect(Buffer.byteLength(firstHash, 'utf8')).toBeLessThan(512); // 512 bytes is maximum size of _id field + expect(Buffer.byteLength(secondHash, 'utf8')).toBeLessThan(512); + expect(firstHash).not.toEqual(secondHash); + }); + test('two docs with same index, different id, and same version should have different id', () => { + const findex = 'myfakeindex'; + const fid = 'somefakeid'; + const fid2 = 'somefakeid2'; + const version = '1'; + const ruleId = 'rule-1'; + // 'myfakeindexsomefakeid1rule-1' + const firstGeneratedHash = '342404d620be4344d6d90dd0461d1d1848aec457944d5c5f40cc0cbfedb36679'; + // 'myfakeindexsomefakeid21rule-1' + const secondGeneratedHash = + '7d33faea18159fd010c4b79890620e8b12cdc88ec1d370149d0e5552ce860255'; + const firstHash = generateId(findex, fid, version, ruleId); + const secondHash = generateId(findex, fid2, version, ruleId); + expect(firstHash).toEqual(firstGeneratedHash); + expect(secondHash).toEqual(secondGeneratedHash); + expect(Buffer.byteLength(firstHash, 'utf8')).toBeLessThan(512); // 512 bytes is maximum size of _id field + expect(Buffer.byteLength(secondHash, 'utf8')).toBeLessThan(512); + expect(firstHash).not.toEqual(secondHash); + }); + test('two docs with same index, same id, and different version should have different id', () => { + const findex = 'myfakeindex'; + const fid = 'somefakeid'; + const version = '1'; + const version2 = '2'; + const ruleId = 'rule-1'; + // 'myfakeindexsomefakeid1rule-1' + const firstGeneratedHash = '342404d620be4344d6d90dd0461d1d1848aec457944d5c5f40cc0cbfedb36679'; + // myfakeindexsomefakeid2rule-1' + const secondGeneratedHash = + 'f016f3071fa9df9221d2fb2ba92389d4d388a4347c6ec7a4012c01cb1c640a40'; + const firstHash = generateId(findex, fid, version, ruleId); + const secondHash = generateId(findex, fid, version2, ruleId); + expect(firstHash).toEqual(firstGeneratedHash); + expect(secondHash).toEqual(secondGeneratedHash); + expect(Buffer.byteLength(firstHash, 'utf8')).toBeLessThan(512); // 512 bytes is maximum size of _id field + expect(Buffer.byteLength(secondHash, 'utf8')).toBeLessThan(512); + expect(firstHash).not.toEqual(secondHash); + }); + test('Ensure generated id is less than 512 bytes, even for really really long strings', () => { + const longIndexName = + 'myfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindex'; + const fid = 'somefakeid'; + const version = '1'; + const ruleId = 'rule-1'; + const firstHash = generateId(longIndexName, fid, version, ruleId); + expect(Buffer.byteLength(firstHash, 'utf8')).toBeLessThan(512); // 512 bytes is maximum size of _id field + }); + test('two docs with same index, same id, same version number, and different rule ids should have different id', () => { + const findex = 'myfakeindex'; + const fid = 'somefakeid'; + const version = '1'; + const ruleId = 'rule-1'; + const ruleId2 = 'rule-2'; + // 'myfakeindexsomefakeid1rule-1' + const firstGeneratedHash = '342404d620be4344d6d90dd0461d1d1848aec457944d5c5f40cc0cbfedb36679'; + // myfakeindexsomefakeid1rule-2' + const secondGeneratedHash = + '1eb04f997086f8b3b143d4d9b18ac178c4a7423f71a5dad9ba8b9e92603c6863'; + const firstHash = generateId(findex, fid, version, ruleId); + const secondHash = generateId(findex, fid, version, ruleId2); + expect(firstHash).toEqual(firstGeneratedHash); + expect(secondHash).toEqual(secondGeneratedHash); + expect(Buffer.byteLength(firstHash, 'utf8')).toBeLessThan(512); // 512 bytes is maximum size of _id field + expect(Buffer.byteLength(secondHash, 'utf8')).toBeLessThan(512); + expect(firstHash).not.toEqual(secondHash); + }); + }); + test('create successful bulk create', async () => { + const sampleParams = sampleRuleAlertParams(); + const sampleSearchResult = sampleDocSearchResultsNoSortId; + mockService.callCluster.mockReturnValueOnce({ + took: 100, + errors: false, + items: [ + { + fakeItemValue: 'fakeItemKey', + }, + ], + }); + const successfulsingleBulkCreate = await singleBulkCreate({ + someResult: sampleSearchResult(), + ruleParams: sampleParams, + services: mockService, + logger: mockLogger, + id: sampleRuleGuid, + signalsIndex: DEFAULT_SIGNALS_INDEX, + name: 'rule-name', + createdBy: 'elastic', + updatedBy: 'elastic', + interval: '5m', + enabled: true, + tags: ['some fake tag 1', 'some fake tag 2'], + }); + expect(successfulsingleBulkCreate).toEqual(true); + }); + test('create successful bulk create with docs with no versioning', async () => { + const sampleParams = sampleRuleAlertParams(); + const sampleSearchResult = sampleDocSearchResultsNoSortIdNoVersion; + mockService.callCluster.mockReturnValueOnce({ + took: 100, + errors: false, + items: [ + { + fakeItemValue: 'fakeItemKey', + }, + ], + }); + const successfulsingleBulkCreate = await singleBulkCreate({ + someResult: sampleSearchResult(), + ruleParams: sampleParams, + services: mockService, + logger: mockLogger, + id: sampleRuleGuid, + signalsIndex: DEFAULT_SIGNALS_INDEX, + name: 'rule-name', + createdBy: 'elastic', + updatedBy: 'elastic', + interval: '5m', + enabled: true, + tags: ['some fake tag 1', 'some fake tag 2'], + }); + expect(successfulsingleBulkCreate).toEqual(true); + }); + test('create unsuccessful bulk create due to empty search results', async () => { + const sampleParams = sampleRuleAlertParams(); + const sampleSearchResult = sampleEmptyDocSearchResults; + mockService.callCluster.mockReturnValue(false); + const successfulsingleBulkCreate = await singleBulkCreate({ + someResult: sampleSearchResult, + ruleParams: sampleParams, + services: mockService, + logger: mockLogger, + id: sampleRuleGuid, + signalsIndex: DEFAULT_SIGNALS_INDEX, + name: 'rule-name', + createdBy: 'elastic', + updatedBy: 'elastic', + interval: '5m', + enabled: true, + tags: ['some fake tag 1', 'some fake tag 2'], + }); + expect(successfulsingleBulkCreate).toEqual(true); + }); + test('create successful bulk create when bulk create has errors', async () => { + const sampleParams = sampleRuleAlertParams(); + const sampleSearchResult = sampleDocSearchResultsNoSortId; + mockService.callCluster.mockReturnValue(sampleBulkCreateDuplicateResult); + const successfulsingleBulkCreate = await singleBulkCreate({ + someResult: sampleSearchResult(), + ruleParams: sampleParams, + services: mockService, + logger: mockLogger, + id: sampleRuleGuid, + signalsIndex: DEFAULT_SIGNALS_INDEX, + name: 'rule-name', + createdBy: 'elastic', + updatedBy: 'elastic', + interval: '5m', + enabled: true, + tags: ['some fake tag 1', 'some fake tag 2'], + }); + expect(mockLogger.error).toHaveBeenCalled(); + expect(successfulsingleBulkCreate).toEqual(true); + }); +}); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.ts new file mode 100644 index 000000000000..40b2eeab938d --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.ts @@ -0,0 +1,106 @@ +/* + * 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 { performance } from 'perf_hooks'; +import { AlertServices } from '../../../../../alerting/server/types'; +import { SignalSearchResponse, BulkResponse } from './types'; +import { RuleTypeParams } from '../types'; +import { generateId } from './utils'; +import { buildBulkBody } from './build_bulk_body'; +import { Logger } from '../../../../../../../../src/core/server'; + +interface SingleBulkCreateParams { + someResult: SignalSearchResponse; + ruleParams: RuleTypeParams; + services: AlertServices; + logger: Logger; + id: string; + signalsIndex: string; + name: string; + createdBy: string; + updatedBy: string; + interval: string; + enabled: boolean; + tags: string[]; +} + +// Bulk Index documents. +export const singleBulkCreate = async ({ + someResult, + ruleParams, + services, + logger, + id, + signalsIndex, + name, + createdBy, + updatedBy, + interval, + enabled, + tags, +}: SingleBulkCreateParams): Promise => { + if (someResult.hits.hits.length === 0) { + return true; + } + // index documents after creating an ID based on the + // source documents' originating index, and the original + // document _id. This will allow two documents from two + // different indexes with the same ID to be + // indexed, and prevents us from creating any updates + // to the documents once inserted into the signals index, + // while preventing duplicates from being added to the + // signals index if rules are re-run over the same time + // span. Also allow for versioning. + const bulkBody = someResult.hits.hits.flatMap(doc => [ + { + create: { + _index: signalsIndex, + _id: generateId( + doc._index, + doc._id, + doc._version ? doc._version.toString() : '', + ruleParams.ruleId ?? '' + ), + }, + }, + buildBulkBody({ doc, ruleParams, id, name, createdBy, updatedBy, interval, enabled, tags }), + ]); + const time1 = performance.now(); + const firstResult: BulkResponse = await services.callCluster('bulk', { + index: signalsIndex, + refresh: false, + body: bulkBody, + }); + const time2 = performance.now(); + logger.debug( + `individual bulk process time took: ${Number(time2 - time1).toFixed(2)} milliseconds` + ); + logger.debug(`took property says bulk took: ${firstResult.took} milliseconds`); + if (firstResult.errors) { + // go through the response status errors and see what + // types of errors they are, count them up, and log them. + const errorCountMap = firstResult.items.reduce((acc: { [key: string]: number }, item) => { + if (item.create.error) { + const responseStatusKey = item.create.status.toString(); + acc[responseStatusKey] = acc[responseStatusKey] ? acc[responseStatusKey] + 1 : 1; + } + return acc; + }, {}); + /* + the logging output below should look like + {'409': 55} + which is read as "there were 55 counts of 409 errors returned from bulk create" + */ + logger.error( + `[-] bulkResponse had errors with response statuses:counts of...\n${JSON.stringify( + errorCountMap, + null, + 2 + )}` + ); + } + return true; +}; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_search_after.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_search_after.test.ts new file mode 100644 index 000000000000..a5d1f66d3089 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_search_after.test.ts @@ -0,0 +1,73 @@ +/* + * 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 { savedObjectsClientMock } from 'src/core/server/mocks'; +import { + sampleRuleAlertParams, + sampleDocSearchResultsNoSortId, + mockLogger, + sampleDocSearchResultsWithSortId, +} from './__mocks__/es_results'; +import { singleSearchAfter } from './single_search_after'; + +export const mockService = { + callCluster: jest.fn(), + alertInstanceFactory: jest.fn(), + savedObjectsClient: savedObjectsClientMock.create(), +}; + +describe('singleSearchAfter', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + test('if singleSearchAfter works without a given sort id', async () => { + let searchAfterSortId; + const sampleParams = sampleRuleAlertParams(); + mockService.callCluster.mockReturnValue(sampleDocSearchResultsNoSortId); + await expect( + singleSearchAfter({ + searchAfterSortId, + ruleParams: sampleParams, + services: mockService, + logger: mockLogger, + pageSize: 1, + filter: undefined, + }) + ).rejects.toThrow('Attempted to search after with empty sort id'); + }); + test('if singleSearchAfter works with a given sort id', async () => { + const searchAfterSortId = '1234567891111'; + const sampleParams = sampleRuleAlertParams(); + mockService.callCluster.mockReturnValue(sampleDocSearchResultsWithSortId); + const searchAfterResult = await singleSearchAfter({ + searchAfterSortId, + ruleParams: sampleParams, + services: mockService, + logger: mockLogger, + pageSize: 1, + filter: undefined, + }); + expect(searchAfterResult).toEqual(sampleDocSearchResultsWithSortId); + }); + test('if singleSearchAfter throws error', async () => { + const searchAfterSortId = '1234567891111'; + const sampleParams = sampleRuleAlertParams(); + mockService.callCluster.mockImplementation(async () => { + throw Error('Fake Error'); + }); + await expect( + singleSearchAfter({ + searchAfterSortId, + ruleParams: sampleParams, + services: mockService, + logger: mockLogger, + pageSize: 1, + filter: undefined, + }) + ).rejects.toThrow('Fake Error'); + }); +}); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_search_after.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_search_after.ts new file mode 100644 index 000000000000..3a99500cb343 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_search_after.ts @@ -0,0 +1,52 @@ +/* + * 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 { RuleTypeParams } from '../types'; +import { AlertServices } from '../../../../../alerting/server/types'; +import { Logger } from '../../../../../../../../src/core/server'; +import { SignalSearchResponse } from './types'; +import { buildEventsSearchQuery } from './build_events_query'; + +interface SingleSearchAfterParams { + searchAfterSortId: string | undefined; + ruleParams: RuleTypeParams; + services: AlertServices; + logger: Logger; + pageSize: number; + filter: unknown; +} + +// utilize search_after for paging results into bulk. +export const singleSearchAfter = async ({ + searchAfterSortId, + ruleParams, + services, + filter, + logger, + pageSize, +}: SingleSearchAfterParams): Promise => { + if (searchAfterSortId == null) { + throw Error('Attempted to search after with empty sort id'); + } + try { + const searchAfterQuery = buildEventsSearchQuery({ + index: ruleParams.index, + from: ruleParams.from, + to: ruleParams.to, + filter, + size: pageSize, + searchAfterSortId, + }); + const nextSearchAfterResult: SignalSearchResponse = await services.callCluster( + 'search', + searchAfterQuery + ); + return nextSearchAfterResult; + } catch (exc) { + logger.error(`[-] nextSearchAfter threw an error ${exc}`); + throw exc; + } +}; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/types.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/types.ts new file mode 100644 index 000000000000..213ceb29a6e2 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/types.ts @@ -0,0 +1,123 @@ +/* + * 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 { RuleAlertParams, OutputRuleAlertRest } from '../types'; +import { SearchResponse } from '../../types'; +import { RequestFacade } from '../../../types'; +import { AlertType, State, AlertExecutorOptions } from '../../../../../alerting/server/types'; + +export interface SignalsParams { + signalIds: string[] | undefined | null; + query: object | undefined | null; + status: 'open' | 'closed'; +} + +export type SignalsRestParams = Omit & { + signal_ids: SignalsParams['signalIds']; +}; + +export interface SignalsRequest extends RequestFacade { + payload: SignalsRestParams; +} + +export type SearchTypes = + | string + | string[] + | number + | number[] + | boolean + | boolean[] + | object + | object[]; + +export interface SignalSource { + [key: string]: SearchTypes; + '@timestamp': string; +} + +export interface BulkResponse { + took: number; + errors: boolean; + items: [ + { + create: { + _index: string; + _type?: string; + _id: string; + _version: number; + result?: string; + _shards?: { + total: number; + successful: number; + failed: number; + }; + _seq_no?: number; + _primary_term?: number; + status: number; + error?: { + type: string; + reason: string; + index_uuid?: string; + shard: string; + index: string; + }; + }; + } + ]; +} + +export interface MGetResponse { + docs: GetResponse[]; +} +export interface GetResponse { + _index: string; + _type: string; + _id: string; + _version: number; + _seq_no: number; + _primary_term: number; + found: boolean; + _source: SearchTypes; +} + +export type SignalSearchResponse = SearchResponse; +export type SignalSourceHit = SignalSearchResponse['hits']['hits'][0]; + +export type RuleExecutorOptions = Omit & { + params: RuleAlertParams & { + scrollSize: number; + scrollLock: string; + }; +}; + +// This returns true because by default a RuleAlertTypeDefinition is an AlertType +// since we are only increasing the strictness of params. +export const isAlertExecutor = (obj: SignalRuleAlertTypeDefinition): obj is AlertType => { + return true; +}; + +export type SignalRuleAlertTypeDefinition = Omit & { + executor: ({ services, params, state }: RuleExecutorOptions) => Promise; +}; + +export interface Signal { + rule: Partial; + parent: { + id: string; + type: string; + index: string; + depth: number; + }; + original_time: string; + original_event?: SearchTypes; + status: 'open' | 'closed'; +} + +export interface SignalHit { + '@timestamp': string; + event: object; + signal: Partial; +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/utils.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/utils.ts new file mode 100644 index 000000000000..f25ce1d90546 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/utils.ts @@ -0,0 +1,16 @@ +/* + * 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 { createHash } from 'crypto'; + +export const generateId = ( + docIndex: string, + docId: string, + version: string, + ruleId: string +): string => + createHash('sha256') + .update(docIndex.concat(docId, version, ruleId)) + .digest('hex'); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/types.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/types.ts new file mode 100644 index 000000000000..d02595c368aa --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/types.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 { esFilters } from '../../../../../../../src/plugins/data/server'; + +export type PartialFilter = Partial; + +export interface IMitreAttack { + id: string; + name: string; + reference: string; +} + +export interface ThreatParams { + framework: string; + tactic: IMitreAttack; + techniques: IMitreAttack[]; +} + +export interface RuleAlertParams { + description: string; + enabled: boolean; + falsePositives: string[]; + filters: PartialFilter[] | undefined | null; + from: string; + immutable: boolean; + index: string[]; + interval: string; + ruleId: string | undefined | null; + language: string | undefined | null; + maxSignals: number; + riskScore: number; + outputIndex: string; + name: string; + query: string | undefined | null; + references: string[]; + savedId: string | undefined | null; + meta: Record | undefined | null; + severity: string; + tags: string[]; + to: string; + threats: ThreatParams[] | undefined | null; + type: 'query' | 'saved_query'; +} + +export type RuleTypeParams = Omit; + +export type RuleAlertParamsRest = Omit< + RuleAlertParams, + 'ruleId' | 'falsePositives' | 'maxSignals' | 'savedId' | 'riskScore' | 'outputIndex' +> & { + rule_id: RuleAlertParams['ruleId']; + false_positives: RuleAlertParams['falsePositives']; + saved_id: RuleAlertParams['savedId']; + max_signals: RuleAlertParams['maxSignals']; + risk_score: RuleAlertParams['riskScore']; + output_index: RuleAlertParams['outputIndex']; +}; + +export type OutputRuleAlertRest = RuleAlertParamsRest & { + id: string; + created_by: string | undefined | null; + updated_by: string | undefined | null; +}; diff --git a/x-pack/legacy/plugins/siem/server/lib/types.ts b/x-pack/legacy/plugins/siem/server/lib/types.ts index e97a07e276dc..9e4e477aa78d 100644 --- a/x-pack/legacy/plugins/siem/server/lib/types.ts +++ b/x-pack/legacy/plugins/siem/server/lib/types.ts @@ -23,7 +23,6 @@ import { Note } from './note/saved_object'; import { PinnedEvent } from './pinned_event/saved_object'; import { Timeline } from './timeline/saved_object'; import { TLS } from './tls'; -import { SearchTypes, OutputRuleAlertRest } from './detection_engine/alerts/types'; export * from './hosts'; @@ -55,25 +54,6 @@ export interface SiemContext { req: FrameworkRequest; } -export interface Signal { - rule: Partial; - parent: { - id: string; - type: string; - index: string; - depth: number; - }; - original_time: string; - original_event?: SearchTypes; - status: 'open' | 'closed'; -} - -export interface SignalHit { - '@timestamp': string; - event: object; - signal: Partial; -} - export interface TotalValue { value: number; relation: string; diff --git a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/search_selection/search_selection.tsx b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/search_selection/search_selection.tsx index 1a270505d61a..368c5aa806fe 100644 --- a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/search_selection/search_selection.tsx +++ b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/search_selection/search_selection.tsx @@ -8,8 +8,8 @@ import { EuiModalBody, EuiModalHeader, EuiModalHeaderTitle } from '@elastic/eui' import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import React, { FC } from 'react'; - -import { SavedObjectFinder } from 'ui/saved_objects/components/saved_object_finder'; +import { SavedObjectFinderUi } from '../../../../../../../../../../src/plugins/kibana_react/public'; +import { useAppDependencies } from '../../../../app_dependencies'; interface SearchSelectionProps { onSearchSelected: (searchId: string, searchType: string) => void; @@ -17,56 +17,63 @@ interface SearchSelectionProps { const fixedPageSize: number = 8; -export const SearchSelection: FC = ({ onSearchSelected }) => ( - <> - - - {' '} - /{' '} - = ({ onSearchSelected }) => { + const { + core: { uiSettings, savedObjects }, + } = useAppDependencies(); + return ( + <> + + + {' '} + /{' '} + + + + + 'search', + name: i18n.translate( + 'xpack.transform.newTransform.searchSelection.savedObjectType.search', + { + defaultMessage: 'Saved search', + } + ), + }, + { + type: 'index-pattern', + getIconForSavedObject: () => 'indexPatternApp', + name: i18n.translate( + 'xpack.transform.newTransform.searchSelection.savedObjectType.indexPattern', + { + defaultMessage: 'Index pattern', + } + ), + }, + ]} + fixedPageSize={fixedPageSize} + uiSettings={uiSettings} + savedObjects={savedObjects} /> - - - - 'search', - name: i18n.translate( - 'xpack.transform.newTransform.searchSelection.savedObjectType.search', - { - defaultMessage: 'Saved search', - } - ), - }, - { - type: 'index-pattern', - getIconForSavedObject: () => 'indexPatternApp', - name: i18n.translate( - 'xpack.transform.newTransform.searchSelection.savedObjectType.indexPattern', - { - defaultMessage: 'Index pattern', - } - ), - }, - ]} - fixedPageSize={fixedPageSize} - /> - - -); + + + ); +}; diff --git a/x-pack/legacy/plugins/transform/public/plugin.ts b/x-pack/legacy/plugins/transform/public/plugin.ts index e7cc83d16b3b..08a3a06fc24f 100644 --- a/x-pack/legacy/plugins/transform/public/plugin.ts +++ b/x-pack/legacy/plugins/transform/public/plugin.ts @@ -27,12 +27,21 @@ const template = `
; +export type AppCore = Pick; export interface AppPlugins { management: { diff --git a/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/app_context.mock.tsx b/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/app_context.mock.tsx new file mode 100644 index 000000000000..de285ee15b59 --- /dev/null +++ b/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/app_context.mock.tsx @@ -0,0 +1,50 @@ +/* + * 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 { ComponentType } from 'enzyme'; +import { + chromeServiceMock, + docLinksServiceMock, + uiSettingsServiceMock, + notificationServiceMock, + httpServiceMock, +} from '../../../../../../../src/core/public/mocks'; +import { AppContextProvider } from '../../../public/np_ready/application/app_context'; + +export const mockContextValue = { + docLinks: docLinksServiceMock.createStartContract(), + chrome: chromeServiceMock.createStartContract(), + legacy: { + TimeBuckets: class MockTimeBuckets { + setBounds(_domain: any) { + return {}; + } + getInterval() { + return { + expression: {}, + }; + } + }, + MANAGEMENT_BREADCRUMB: { text: 'test' }, + licenseStatus: {}, + }, + uiSettings: uiSettingsServiceMock.createSetupContract(), + toasts: notificationServiceMock.createSetupContract().toasts, + euiUtils: { + useChartsTheme: jest.fn(), + }, + // For our test harness, we don't use this mocked out http service + http: httpServiceMock.createSetupContract(), +}; + +export const withAppContext = (Component: ComponentType) => (props: any) => { + return ( + + + + ); +}; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/add_item_form/translations.ts b/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/body_response.ts similarity index 56% rename from x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/add_item_form/translations.ts rename to x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/body_response.ts index 98c15606d88f..3b3df5fd6f87 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/add_item_form/translations.ts +++ b/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/body_response.ts @@ -4,11 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { i18n } from '@kbn/i18n'; +export const wrapBodyResponse = (obj: object) => JSON.stringify({ body: JSON.stringify(obj) }); -export const DELETE = i18n.translate( - 'xpack.siem.detectionEngine.createRule.addItem.deleteDescription', - { - defaultMessage: 'Delete', - } -); +export const unwrapBodyResponse = (string: string) => JSON.parse(JSON.parse(string).body); diff --git a/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/http_requests.ts b/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/http_requests.ts index 2170559dace5..7d9c1e4163d7 100644 --- a/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/http_requests.ts +++ b/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/http_requests.ts @@ -34,7 +34,7 @@ const registerHttpRequestMockHelpers = (server: SinonFakeServer) => { const defaultResponse = { watchHistoryItems: [] }; server.respondWith( 'GET', - `${API_ROOT}/watch/:id/history?startTime=*`, + `${API_ROOT}/watch/:id/history`, mockResponse(defaultResponse, response) ); }; diff --git a/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/index.ts b/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/index.ts index ad005078db0a..814028fe599f 100644 --- a/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/index.ts +++ b/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/index.ts @@ -11,7 +11,7 @@ import { setup as watchCreateThresholdSetup } from './watch_create_threshold.hel import { setup as watchEditSetup } from './watch_edit.helpers'; export { nextTick, getRandomString, findTestSubject, TestBed } from '../../../../../../test_utils'; - +export { wrapBodyResponse, unwrapBodyResponse } from './body_response'; export { setupEnvironment } from './setup_environment'; export const pageHelpers = { diff --git a/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/setup_environment.ts b/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/setup_environment.ts index 806840a7821f..7e748073c1c6 100644 --- a/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/setup_environment.ts +++ b/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/setup_environment.ts @@ -7,9 +7,17 @@ import axios from 'axios'; import axiosXhrAdapter from 'axios/lib/adapters/xhr'; import { init as initHttpRequests } from './http_requests'; -import { setHttpClient, setSavedObjectsClient } from '../../../public/lib/api'; +import { setHttpClient, setSavedObjectsClient } from '../../../public/np_ready/application/lib/api'; const mockHttpClient = axios.create({ adapter: axiosXhrAdapter }); +mockHttpClient.interceptors.response.use( + res => { + return res.data; + }, + rej => { + return Promise.reject(rej); + } +); const mockSavedObjectsClient = () => { return { @@ -23,7 +31,7 @@ export const setupEnvironment = () => { // @ts-ignore setHttpClient(mockHttpClient); - setSavedObjectsClient(mockSavedObjectsClient()); + setSavedObjectsClient(mockSavedObjectsClient() as any); return { server, diff --git a/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/watch_create_json.helpers.ts b/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/watch_create_json.helpers.ts index bea215281a4b..dafcf3a7070d 100644 --- a/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/watch_create_json.helpers.ts +++ b/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/watch_create_json.helpers.ts @@ -3,10 +3,11 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import { withAppContext } from './app_context.mock'; import { registerTestBed, TestBed, TestBedConfig } from '../../../../../../test_utils'; -import { WatchEdit } from '../../../public/sections/watch_edit/components/watch_edit'; +import { WatchEdit } from '../../../public/np_ready/application/sections/watch_edit/components/watch_edit'; import { ROUTES, WATCH_TYPES } from '../../../common/constants'; -import { registerRouter } from '../../../public/lib/navigation'; +import { registerRouter } from '../../../public/np_ready/application/lib/navigation'; const testBedConfig: TestBedConfig = { memoryRouter: { @@ -17,7 +18,7 @@ const testBedConfig: TestBedConfig = { doMountAsync: true, }; -const initTestBed = registerTestBed(WatchEdit, testBedConfig); +const initTestBed = registerTestBed(withAppContext(WatchEdit), testBedConfig); export interface WatchCreateJsonTestBed extends TestBed { actions: { diff --git a/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/watch_create_threshold.helpers.ts b/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/watch_create_threshold.helpers.ts index e33ae0203622..8cebe8ce2622 100644 --- a/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/watch_create_threshold.helpers.ts +++ b/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/watch_create_threshold.helpers.ts @@ -4,9 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ import { registerTestBed, TestBed, TestBedConfig } from '../../../../../../test_utils'; -import { WatchEdit } from '../../../public/sections/watch_edit/components/watch_edit'; +import { WatchEdit } from '../../../public/np_ready/application/sections/watch_edit/components/watch_edit'; import { ROUTES, WATCH_TYPES } from '../../../common/constants'; -import { registerRouter } from '../../../public/lib/navigation'; +import { registerRouter } from '../../../public/np_ready/application/lib/navigation'; +import { withAppContext } from './app_context.mock'; const testBedConfig: TestBedConfig = { memoryRouter: { @@ -17,7 +18,7 @@ const testBedConfig: TestBedConfig = { doMountAsync: true, }; -const initTestBed = registerTestBed(WatchEdit, testBedConfig); +const initTestBed = registerTestBed(withAppContext(WatchEdit), testBedConfig); export interface WatchCreateThresholdTestBed extends TestBed { actions: { diff --git a/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/watch_edit.helpers.ts b/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/watch_edit.helpers.ts index d0b458e30c70..187f4dcaa0a7 100644 --- a/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/watch_edit.helpers.ts +++ b/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/watch_edit.helpers.ts @@ -4,10 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ import { registerTestBed, TestBed, TestBedConfig } from '../../../../../../test_utils'; -import { WatchEdit } from '../../../public/sections/watch_edit/components/watch_edit'; +import { WatchEdit } from '../../../public/np_ready/application/sections/watch_edit/components/watch_edit'; import { ROUTES } from '../../../common/constants'; -import { registerRouter } from '../../../public/lib/navigation'; +import { registerRouter } from '../../../public/np_ready/application/lib/navigation'; import { WATCH_ID } from './constants'; +import { withAppContext } from './app_context.mock'; const testBedConfig: TestBedConfig = { memoryRouter: { @@ -18,7 +19,7 @@ const testBedConfig: TestBedConfig = { doMountAsync: true, }; -const initTestBed = registerTestBed(WatchEdit, testBedConfig); +const initTestBed = registerTestBed(withAppContext(WatchEdit), testBedConfig); export interface WatchEditTestBed extends TestBed { actions: { diff --git a/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/watch_list.helpers.ts b/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/watch_list.helpers.ts index 0d3ecaa7a2b9..e33327ea42ff 100644 --- a/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/watch_list.helpers.ts +++ b/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/watch_list.helpers.ts @@ -13,8 +13,9 @@ import { TestBedConfig, nextTick, } from '../../../../../../test_utils'; -import { WatchList } from '../../../public/sections/watch_list/components/watch_list'; +import { WatchList } from '../../../public/np_ready/application/sections/watch_list/components/watch_list'; import { ROUTES } from '../../../common/constants'; +import { withAppContext } from './app_context.mock'; const testBedConfig: TestBedConfig = { memoryRouter: { @@ -23,7 +24,7 @@ const testBedConfig: TestBedConfig = { doMountAsync: true, }; -const initTestBed = registerTestBed(WatchList, testBedConfig); +const initTestBed = registerTestBed(withAppContext(WatchList), testBedConfig); export interface WatchListTestBed extends TestBed { actions: { diff --git a/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/watch_status.helpers.ts b/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/watch_status.helpers.ts index 22d57f255ebe..e7bffe8924e3 100644 --- a/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/watch_status.helpers.ts +++ b/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/watch_status.helpers.ts @@ -13,9 +13,10 @@ import { TestBedConfig, nextTick, } from '../../../../../../test_utils'; -import { WatchStatus } from '../../../public/sections/watch_status/components/watch_status'; +import { WatchStatus } from '../../../public/np_ready/application/sections/watch_status/components/watch_status'; import { ROUTES } from '../../../common/constants'; import { WATCH_ID } from './constants'; +import { withAppContext } from './app_context.mock'; const testBedConfig: TestBedConfig = { memoryRouter: { @@ -25,7 +26,7 @@ const testBedConfig: TestBedConfig = { doMountAsync: true, }; -const initTestBed = registerTestBed(WatchStatus, testBedConfig); +const initTestBed = registerTestBed(withAppContext(WatchStatus), testBedConfig); export interface WatchStatusTestBed extends TestBed { actions: { diff --git a/x-pack/legacy/plugins/watcher/__jest__/client_integration/watch_create_json.test.ts b/x-pack/legacy/plugins/watcher/__jest__/client_integration/watch_create_json.test.ts index f45dbe156723..4c893978ee5c 100644 --- a/x-pack/legacy/plugins/watcher/__jest__/client_integration/watch_create_json.test.ts +++ b/x-pack/legacy/plugins/watcher/__jest__/client_integration/watch_create_json.test.ts @@ -4,22 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ import { act } from 'react-dom/test-utils'; -import { setupEnvironment, pageHelpers, nextTick } from './helpers'; +import { setupEnvironment, pageHelpers, nextTick, wrapBodyResponse } from './helpers'; import { WatchCreateJsonTestBed } from './helpers/watch_create_json.helpers'; import { WATCH } from './helpers/constants'; -import defaultWatchJson from '../../public/models/watch/default_watch.json'; +import defaultWatchJson from '../../public/np_ready/application/models/watch/default_watch.json'; import { getExecuteDetails } from '../../test/fixtures'; -jest.mock('ui/chrome', () => ({ - breadcrumbs: { set: () => {} }, - addBasePath: (path: string) => path || '/api/watcher', -})); - -jest.mock('ui/time_buckets', () => {}); - const { setup } = pageHelpers.watchCreateJson; -describe.skip(' create route', () => { +describe(' create route', () => { const { server, httpRequestsMockHelpers } = setupEnvironment(); let testBed: WatchCreateJsonTestBed; @@ -107,7 +100,7 @@ describe.skip(' create route', () => { 'There are {{ctx.payload.hits.total}} documents in your index. Threshold is 10.'; expect(latestRequest.requestBody).toEqual( - JSON.stringify({ + wrapBodyResponse({ id: watch.id, name: watch.name, type: watch.type, @@ -194,7 +187,7 @@ describe.skip(' create route', () => { }; expect(latestRequest.requestBody).toEqual( - JSON.stringify({ + wrapBodyResponse({ executeDetails: getExecuteDetails({ actionModes, }), @@ -258,7 +251,7 @@ describe.skip(' create route', () => { const scheduledTime = `now+${SCHEDULED_TIME}s`; expect(latestRequest.requestBody).toEqual( - JSON.stringify({ + wrapBodyResponse({ executeDetails: getExecuteDetails({ triggerData: { triggeredTime, diff --git a/x-pack/legacy/plugins/watcher/__jest__/client_integration/watch_create_threshold.test.tsx b/x-pack/legacy/plugins/watcher/__jest__/client_integration/watch_create_threshold.test.tsx index 62cfd9218209..36a5c150eead 100644 --- a/x-pack/legacy/plugins/watcher/__jest__/client_integration/watch_create_threshold.test.tsx +++ b/x-pack/legacy/plugins/watcher/__jest__/client_integration/watch_create_threshold.test.tsx @@ -7,7 +7,13 @@ import React from 'react'; import { act } from 'react-dom/test-utils'; import axiosXhrAdapter from 'axios/lib/adapters/xhr'; import axios from 'axios'; -import { setupEnvironment, pageHelpers, nextTick } from './helpers'; +import { + setupEnvironment, + pageHelpers, + nextTick, + wrapBodyResponse, + unwrapBodyResponse, +} from './helpers'; import { WatchCreateThresholdTestBed } from './helpers/watch_create_threshold.helpers'; import { getExecuteDetails } from '../../test/fixtures'; import { WATCH_TYPES } from '../../common/constants'; @@ -42,31 +48,8 @@ const WATCH_VISUALIZE_DATA = { const mockHttpClient = axios.create({ adapter: axiosXhrAdapter }); -jest.mock('ui/chrome', () => ({ - breadcrumbs: { set: () => {} }, - addBasePath: (path: string) => path || '/api/watcher', - getUiSettingsClient: () => ({ - get: () => {}, - isDefault: () => true, - }), -})); - -jest.mock('ui/time_buckets', () => { - class MockTimeBuckets { - setBounds(_domain: any) { - return {}; - } - getInterval() { - return { - expression: {}, - }; - } - } - return { TimeBuckets: MockTimeBuckets }; -}); - -jest.mock('../../public/lib/api', () => ({ - ...jest.requireActual('../../public/lib/api'), +jest.mock('../../public/np_ready/application/lib/api', () => ({ + ...jest.requireActual('../../public/np_ready/application/lib/api'), loadIndexPatterns: async () => { const INDEX_PATTERNS = [ { attributes: { title: 'index1' } }, @@ -85,7 +68,7 @@ jest.mock('@elastic/eui', () => ({ EuiComboBox: (props: any) => ( { + onChange={(syntheticEvent: any) => { props.onChange([syntheticEvent['0']]); }} /> @@ -94,7 +77,7 @@ jest.mock('@elastic/eui', () => ({ const { setup } = pageHelpers.watchCreateThreshold; -describe.skip(' create route', () => { +describe(' create route', () => { const { server, httpRequestsMockHelpers } = setupEnvironment(); let testBed: WatchCreateThresholdTestBed; @@ -105,12 +88,9 @@ describe.skip(' create route', () => { describe('on component mount', () => { beforeEach(async () => { testBed = await setup(); - - await act(async () => { - const { component } = testBed; - await nextTick(); - component.update(); - }); + const { component } = testBed; + await nextTick(); + component.update(); }); test('should set the correct page title', () => { @@ -125,13 +105,6 @@ describe.skip(' create route', () => { httpRequestsMockHelpers.setLoadEsFieldsResponse({ fields: ES_FIELDS }); httpRequestsMockHelpers.setLoadSettingsResponse(SETTINGS); httpRequestsMockHelpers.setLoadWatchVisualizeResponse(WATCH_VISUALIZE_DATA); - - testBed = await setup(); - - await act(async () => { - await nextTick(); - testBed.component.update(); - }); }); describe('form validation', () => { @@ -173,7 +146,7 @@ describe.skip(' create route', () => { expect(find('saveWatchButton').props().disabled).toEqual(true); }); - test('it should enable the Create button and render additonal content with valid fields', async () => { + test('it should enable the Create button and render additional content with valid fields', async () => { const { form, find, component, exists } = testBed; form.setInputValue('nameInput', 'my_test_watch'); @@ -192,39 +165,30 @@ describe.skip(' create route', () => { expect(exists('watchActionsPanel')).toBe(true); }); - describe('watch conditions', () => { - beforeEach(async () => { - const { form, find, component } = testBed; + // Looks like there is an issue with using 'mockComboBox'. + describe.skip('watch conditions', () => { + beforeEach(() => { + const { form, find } = testBed; // Name, index and time fields are required before the watch condition expression renders form.setInputValue('nameInput', 'my_test_watch'); - find('mockComboBox').simulate('change', [{ label: 'index1', value: 'index1' }]); // Using mocked EuiComboBox - form.setInputValue('watchTimeFieldSelect', '@timestamp'); - - await act(async () => { - await nextTick(); - component.update(); + act(() => { + find('mockComboBox').simulate('change', [{ label: 'index1', value: 'index1' }]); // Using mocked EuiComboBox }); + form.setInputValue('watchTimeFieldSelect', '@timestamp'); }); - test('should require a threshold value', async () => { - const { form, find, component } = testBed; - - find('watchThresholdButton').simulate('click'); + test('should require a threshold value', () => { + const { form, find } = testBed; - // Provide invalid value - form.setInputValue('watchThresholdInput', ''); - - expect(form.getErrorsMessages()).toContain('A value is required.'); - - // Provide valid value - form.setInputValue('watchThresholdInput', '0'); - - await act(async () => { - await nextTick(); - component.update(); + act(() => { + find('watchThresholdButton').simulate('click'); + // Provide invalid value + form.setInputValue('watchThresholdInput', ''); + // Provide valid value + form.setInputValue('watchThresholdInput', '0'); }); - + expect(form.getErrorsMessages()).toContain('A value is required.'); expect(form.getErrorsMessages().length).toEqual(0); }); }); @@ -273,7 +237,7 @@ describe.skip(' create route', () => { const latestRequest = server.requests[server.requests.length - 1]; const thresholdWatch = { - id: JSON.parse(latestRequest.requestBody).watch.id, // watch ID is created dynamically + id: unwrapBodyResponse(latestRequest.requestBody).watch.id, // watch ID is created dynamically name: WATCH_NAME, type: WATCH_TYPES.THRESHOLD, isNew: true, @@ -300,7 +264,7 @@ describe.skip(' create route', () => { }; expect(latestRequest.requestBody).toEqual( - JSON.stringify({ + wrapBodyResponse({ executeDetails: getExecuteDetails({ actionModes: { logging_1: 'force_execute', @@ -341,7 +305,7 @@ describe.skip(' create route', () => { const latestRequest = server.requests[server.requests.length - 1]; const thresholdWatch = { - id: JSON.parse(latestRequest.requestBody).watch.id, // watch ID is created dynamically + id: unwrapBodyResponse(latestRequest.requestBody).watch.id, // watch ID is created dynamically name: WATCH_NAME, type: WATCH_TYPES.THRESHOLD, isNew: true, @@ -367,7 +331,7 @@ describe.skip(' create route', () => { }; expect(latestRequest.requestBody).toEqual( - JSON.stringify({ + wrapBodyResponse({ executeDetails: getExecuteDetails({ actionModes: { index_1: 'force_execute', @@ -401,7 +365,7 @@ describe.skip(' create route', () => { const latestRequest = server.requests[server.requests.length - 1]; const thresholdWatch = { - id: JSON.parse(latestRequest.requestBody).watch.id, // watch ID is created dynamically + id: unwrapBodyResponse(latestRequest.requestBody).watch.id, // watch ID is created dynamically name: WATCH_NAME, type: WATCH_TYPES.THRESHOLD, isNew: true, @@ -430,7 +394,7 @@ describe.skip(' create route', () => { }; expect(latestRequest.requestBody).toEqual( - JSON.stringify({ + wrapBodyResponse({ executeDetails: getExecuteDetails({ actionModes: { slack_1: 'force_execute', @@ -471,7 +435,7 @@ describe.skip(' create route', () => { const latestRequest = server.requests[server.requests.length - 1]; const thresholdWatch = { - id: JSON.parse(latestRequest.requestBody).watch.id, // watch ID is created dynamically + id: unwrapBodyResponse(latestRequest.requestBody).watch.id, // watch ID is created dynamically name: WATCH_NAME, type: WATCH_TYPES.THRESHOLD, isNew: true, @@ -504,7 +468,7 @@ describe.skip(' create route', () => { }; expect(latestRequest.requestBody).toEqual( - JSON.stringify({ + wrapBodyResponse({ executeDetails: getExecuteDetails({ actionModes: { email_1: 'force_execute', @@ -559,7 +523,7 @@ describe.skip(' create route', () => { const latestRequest = server.requests[server.requests.length - 1]; const thresholdWatch = { - id: JSON.parse(latestRequest.requestBody).watch.id, // watch ID is created dynamically + id: unwrapBodyResponse(latestRequest.requestBody).watch.id, // watch ID is created dynamically name: WATCH_NAME, type: WATCH_TYPES.THRESHOLD, isNew: true, @@ -594,7 +558,7 @@ describe.skip(' create route', () => { }; expect(latestRequest.requestBody).toEqual( - JSON.stringify({ + wrapBodyResponse({ executeDetails: getExecuteDetails({ actionModes: { webhook_1: 'force_execute', @@ -645,7 +609,7 @@ describe.skip(' create route', () => { const latestRequest = server.requests[server.requests.length - 1]; const thresholdWatch = { - id: JSON.parse(latestRequest.requestBody).watch.id, // watch ID is created dynamically + id: unwrapBodyResponse(latestRequest.requestBody).watch.id, // watch ID is created dynamically name: WATCH_NAME, type: WATCH_TYPES.THRESHOLD, isNew: true, @@ -682,7 +646,7 @@ describe.skip(' create route', () => { }; expect(latestRequest.requestBody).toEqual( - JSON.stringify({ + wrapBodyResponse({ executeDetails: getExecuteDetails({ actionModes: { jira_1: 'force_execute', @@ -723,7 +687,7 @@ describe.skip(' create route', () => { const latestRequest = server.requests[server.requests.length - 1]; const thresholdWatch = { - id: JSON.parse(latestRequest.requestBody).watch.id, // watch ID is created dynamically + id: unwrapBodyResponse(latestRequest.requestBody).watch.id, // watch ID is created dynamically name: WATCH_NAME, type: WATCH_TYPES.THRESHOLD, isNew: true, @@ -750,7 +714,7 @@ describe.skip(' create route', () => { }; expect(latestRequest.requestBody).toEqual( - JSON.stringify({ + wrapBodyResponse({ executeDetails: getExecuteDetails({ actionModes: { pagerduty_1: 'force_execute', @@ -784,7 +748,7 @@ describe.skip(' create route', () => { const latestRequest = server.requests[server.requests.length - 1]; const thresholdWatch = { - id: JSON.parse(latestRequest.requestBody).id, // watch ID is created dynamically + id: unwrapBodyResponse(latestRequest.requestBody).id, // watch ID is created dynamically name: WATCH_NAME, type: WATCH_TYPES.THRESHOLD, isNew: true, @@ -801,7 +765,7 @@ describe.skip(' create route', () => { threshold: 1000, }; - expect(latestRequest.requestBody).toEqual(JSON.stringify(thresholdWatch)); + expect(latestRequest.requestBody).toEqual(wrapBodyResponse(thresholdWatch)); }); }); }); diff --git a/x-pack/legacy/plugins/watcher/__jest__/client_integration/watch_edit.test.ts b/x-pack/legacy/plugins/watcher/__jest__/client_integration/watch_edit.test.ts index fb9ad934249e..1eee3d3b7e6e 100644 --- a/x-pack/legacy/plugins/watcher/__jest__/client_integration/watch_edit.test.ts +++ b/x-pack/legacy/plugins/watcher/__jest__/client_integration/watch_edit.test.ts @@ -6,36 +6,17 @@ import { act } from 'react-dom/test-utils'; import axiosXhrAdapter from 'axios/lib/adapters/xhr'; import axios from 'axios'; -import { setupEnvironment, pageHelpers, nextTick } from './helpers'; +import { setupEnvironment, pageHelpers, nextTick, wrapBodyResponse } from './helpers'; import { WatchEditTestBed } from './helpers/watch_edit.helpers'; import { WATCH } from './helpers/constants'; -import defaultWatchJson from '../../public/models/watch/default_watch.json'; +import defaultWatchJson from '../../public/np_ready/application/models/watch/default_watch.json'; import { getWatch } from '../../test/fixtures'; import { getRandomString } from '../../../../../test_utils'; const mockHttpClient = axios.create({ adapter: axiosXhrAdapter }); -jest.mock('ui/chrome', () => ({ - breadcrumbs: { set: () => {} }, - addBasePath: (path: string) => path || '/api/watcher', -})); - -jest.mock('ui/time_buckets', () => { - class MockTimeBuckets { - setBounds(_domain: any) { - return {}; - } - getInterval() { - return { - expression: {}, - }; - } - } - return { TimeBuckets: MockTimeBuckets }; -}); - -jest.mock('../../public/lib/api', () => ({ - ...jest.requireActual('../../public/lib/api'), +jest.mock('../../public/np_ready/application/lib/api', () => ({ + ...jest.requireActual('../../public/np_ready/application/lib/api'), loadIndexPatterns: async () => { const INDEX_PATTERNS = [ { attributes: { title: 'index1' } }, @@ -49,7 +30,7 @@ jest.mock('../../public/lib/api', () => ({ const { setup } = pageHelpers.watchEdit; -describe.skip('', () => { +describe('', () => { const { server, httpRequestsMockHelpers } = setupEnvironment(); let testBed: WatchEditTestBed; @@ -110,7 +91,7 @@ describe.skip('', () => { 'There are {{ctx.payload.hits.total}} documents in your index. Threshold is 10.'; expect(latestRequest.requestBody).toEqual( - JSON.stringify({ + wrapBodyResponse({ id: watch.id, name: EDITED_WATCH_NAME, type: watch.type, @@ -202,7 +183,7 @@ describe.skip('', () => { } = watch; expect(latestRequest.requestBody).toEqual( - JSON.stringify({ + wrapBodyResponse({ id, name: EDITED_WATCH_NAME, type, diff --git a/x-pack/legacy/plugins/watcher/__jest__/client_integration/watch_list.test.ts b/x-pack/legacy/plugins/watcher/__jest__/client_integration/watch_list.test.ts index bc2eadb7d9be..a0327c6dfa1d 100644 --- a/x-pack/legacy/plugins/watcher/__jest__/client_integration/watch_list.test.ts +++ b/x-pack/legacy/plugins/watcher/__jest__/client_integration/watch_list.test.ts @@ -18,16 +18,9 @@ import { ROUTES } from '../../common/constants'; const { API_ROOT } = ROUTES; -jest.mock('ui/chrome', () => ({ - breadcrumbs: { set: () => {} }, - addBasePath: (path: string) => path || '/api/watcher', -})); - -jest.mock('ui/time_buckets', () => {}); - const { setup } = pageHelpers.watchList; -describe.skip('', () => { +describe('', () => { const { server, httpRequestsMockHelpers } = setupEnvironment(); let testBed: WatchListTestBed; diff --git a/x-pack/legacy/plugins/watcher/__jest__/client_integration/watch_status.test.ts b/x-pack/legacy/plugins/watcher/__jest__/client_integration/watch_status.test.ts index e12acd2e32cc..973c14893f34 100644 --- a/x-pack/legacy/plugins/watcher/__jest__/client_integration/watch_status.test.ts +++ b/x-pack/legacy/plugins/watcher/__jest__/client_integration/watch_status.test.ts @@ -14,13 +14,6 @@ import { WATCH_STATES, ACTION_STATES } from '../../common/constants'; const { API_ROOT } = ROUTES; -jest.mock('ui/chrome', () => ({ - breadcrumbs: { set: () => {} }, - addBasePath: (path: string) => path || '/api/watcher', -})); - -jest.mock('ui/time_buckets', () => {}); - const { setup } = pageHelpers.watchStatus; const watchHistory1 = getWatchHistory({ startTime: '2019-06-04T01:11:11.294' }); @@ -45,7 +38,7 @@ const watch = { }, }; -describe.skip('', () => { +describe('', () => { const { server, httpRequestsMockHelpers } = setupEnvironment(); let testBed: WatchStatusTestBed; diff --git a/x-pack/legacy/plugins/watcher/kibana.json b/x-pack/legacy/plugins/watcher/kibana.json new file mode 100644 index 000000000000..ccec8a1b7768 --- /dev/null +++ b/x-pack/legacy/plugins/watcher/kibana.json @@ -0,0 +1,9 @@ +{ + "id": "watcher", + "version": "kibana", + "requiredPlugins": [ + "home" + ], + "server": true, + "ui": true +} diff --git a/x-pack/legacy/plugins/watcher/plugin_definition.js b/x-pack/legacy/plugins/watcher/plugin_definition.js deleted file mode 100644 index 4a5946cc4974..000000000000 --- a/x-pack/legacy/plugins/watcher/plugin_definition.js +++ /dev/null @@ -1,46 +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 { resolve } from 'path'; -import { i18n } from '@kbn/i18n'; -import { registerFieldsRoutes } from './server/routes/api/fields'; -import { registerSettingsRoutes } from './server/routes/api/settings'; -import { registerHistoryRoutes } from './server/routes/api/history'; -import { registerIndicesRoutes } from './server/routes/api/indices'; -import { registerLicenseRoutes } from './server/routes/api/license'; -import { registerWatchesRoutes } from './server/routes/api/watches'; -import { registerWatchRoutes } from './server/routes/api/watch'; -import { registerLicenseChecker } from '../../server/lib/register_license_checker'; -import { PLUGIN } from './common/constants'; - -export const pluginDefinition = { - id: PLUGIN.ID, - configPrefix: 'xpack.watcher', - publicDir: resolve(__dirname, 'public'), - require: ['kibana', 'elasticsearch', 'xpack_main'], - uiExports: { - styleSheetPaths: resolve(__dirname, 'public/index.scss'), - managementSections: ['plugins/watcher'], - home: ['plugins/watcher/register_feature'], - }, - init: function (server) { - // Register license checker - registerLicenseChecker( - server, - PLUGIN.ID, - PLUGIN.getI18nName(i18n), - PLUGIN.MINIMUM_LICENSE_REQUIRED - ); - - registerFieldsRoutes(server); - registerHistoryRoutes(server); - registerIndicesRoutes(server); - registerLicenseRoutes(server); - registerSettingsRoutes(server); - registerWatchesRoutes(server); - registerWatchRoutes(server); - }, -}; diff --git a/x-pack/legacy/plugins/watcher/plugin_definition.ts b/x-pack/legacy/plugins/watcher/plugin_definition.ts new file mode 100644 index 000000000000..2da05253fdb3 --- /dev/null +++ b/x-pack/legacy/plugins/watcher/plugin_definition.ts @@ -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 { resolve } from 'path'; +import { plugin } from './server/np_ready'; +import { PLUGIN } from './common/constants'; + +export const pluginDefinition = { + id: PLUGIN.ID, + configPrefix: 'xpack.watcher', + publicDir: resolve(__dirname, 'public'), + require: ['kibana', 'elasticsearch', 'xpack_main'], + uiExports: { + styleSheetPaths: resolve(__dirname, 'public/np_ready/application/index.scss'), + managementSections: ['plugins/watcher/legacy'], + home: ['plugins/watcher/register_feature'], + }, + init(server: any) { + plugin({} as any).setup(server.newPlatform.setup.core, { + __LEGACY: { + route: server.route.bind(server), + plugins: { + watcher: server.plugins[PLUGIN.ID], + xpack_main: server.plugins.xpack_main, + }, + }, + }); + }, +}; diff --git a/x-pack/legacy/plugins/watcher/public/app.html b/x-pack/legacy/plugins/watcher/public/app.html deleted file mode 100644 index 8c7c3eb946ae..000000000000 --- a/x-pack/legacy/plugins/watcher/public/app.html +++ /dev/null @@ -1,3 +0,0 @@ - -
-
\ No newline at end of file diff --git a/x-pack/legacy/plugins/watcher/public/legacy.ts b/x-pack/legacy/plugins/watcher/public/legacy.ts new file mode 100644 index 000000000000..d7b85ccfeb7b --- /dev/null +++ b/x-pack/legacy/plugins/watcher/public/legacy.ts @@ -0,0 +1,146 @@ +/* + * 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 { CoreSetup, App, AppUnmount } from 'src/core/public'; +import { i18n } from '@kbn/i18n'; + +/* Legacy UI imports */ +import { npSetup, npStart } from 'ui/new_platform'; +import routes from 'ui/routes'; +import { management, MANAGEMENT_BREADCRUMB } from 'ui/management'; +import { TimeBuckets } from 'ui/time_buckets'; +// @ts-ignore +import { xpackInfo } from 'plugins/xpack_main/services/xpack_info'; +/* Legacy UI imports */ + +import { plugin } from './np_ready'; +import { PLUGIN } from '../common/constants'; +import { LICENSE_STATUS_INVALID, LICENSE_STATUS_UNAVAILABLE } from '../../../common/constants'; +import { manageAngularLifecycle } from './manage_angular_lifecycle'; + +const template = ` +
+
`; + +let elem: HTMLElement; +let mountApp: () => AppUnmount | Promise; +let unmountApp: AppUnmount | Promise; +routes.when('/management/elasticsearch/watcher/:param1?/:param2?/:param3?/:param4?', { + template, + controller: class WatcherController { + constructor($injector: any, $scope: any) { + const $route = $injector.get('$route'); + const licenseStatus = xpackInfo.get(`features.${PLUGIN.ID}`); + const shimCore: CoreSetup = { + ...npSetup.core, + application: { + ...npSetup.core.application, + register(app: App): void { + mountApp = () => + app.mount(npStart as any, { + element: elem, + appBasePath: '/management/elasticsearch/watcher/', + }); + }, + }, + }; + + // clean up previously rendered React app if one exists + // this happens because of React Router redirects + if (elem) { + ((unmountApp as unknown) as AppUnmount)(); + } + + $scope.$$postDigest(() => { + elem = document.getElementById('watchReactRoot')!; + const instance = plugin(); + instance.setup(shimCore, { + ...(npSetup.plugins as typeof npSetup.plugins & { eui_utils: any }), + __LEGACY: { + MANAGEMENT_BREADCRUMB, + TimeBuckets, + licenseStatus, + }, + }); + + instance.start(npStart.core, npStart.plugins); + + (mountApp() as Promise).then(fn => (unmountApp = fn)); + + manageAngularLifecycle($scope, $route, elem); + }); + } + } as any, + // @ts-ignore + controllerAs: 'watchRoute', +}); + +routes.defaults(/\/management/, { + resolve: { + watcherManagementSection: () => { + const watchesSection = management.getSection('elasticsearch/watcher'); + const licenseStatus = xpackInfo.get(`features.${PLUGIN.ID}`); + const { status } = licenseStatus; + + if (status === LICENSE_STATUS_INVALID || status === LICENSE_STATUS_UNAVAILABLE) { + return watchesSection.hide(); + } + + watchesSection.show(); + }, + }, +}); + +management.getSection('elasticsearch').register('watcher', { + display: i18n.translate('xpack.watcher.sections.watchList.managementSection.watcherDisplayName', { + defaultMessage: 'Watcher', + }), + order: 6, + url: '#/management/elasticsearch/watcher/', +} as any); + +management.getSection('elasticsearch/watcher').register('watches', { + display: i18n.translate('xpack.watcher.sections.watchList.managementSection.watchesDisplayName', { + defaultMessage: 'Watches', + }), + order: 1, +} as any); + +management.getSection('elasticsearch/watcher').register('watch', { + visible: false, +} as any); + +management.getSection('elasticsearch/watcher/watch').register('status', { + display: i18n.translate('xpack.watcher.sections.watchList.managementSection.statusDisplayName', { + defaultMessage: 'Status', + }), + order: 1, + visible: false, +} as any); + +management.getSection('elasticsearch/watcher/watch').register('edit', { + display: i18n.translate('xpack.watcher.sections.watchList.managementSection.editDisplayName', { + defaultMessage: 'Edit', + }), + order: 2, + visible: false, +} as any); + +management.getSection('elasticsearch/watcher/watch').register('new', { + display: i18n.translate( + 'xpack.watcher.sections.watchList.managementSection.newWatchDisplayName', + { + defaultMessage: 'New Watch', + } + ), + order: 1, + visible: false, +} as any); + +management.getSection('elasticsearch/watcher/watch').register('history-item', { + order: 1, + visible: false, +} as any); diff --git a/x-pack/legacy/plugins/watcher/public/lib/documentation_links/documentation_links.ts b/x-pack/legacy/plugins/watcher/public/lib/documentation_links/documentation_links.ts deleted file mode 100644 index 88f23465d33e..000000000000 --- a/x-pack/legacy/plugins/watcher/public/lib/documentation_links/documentation_links.ts +++ /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 { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } from 'ui/documentation_links'; -import { ACTION_TYPES } from '../../../common/constants'; - -const elasticDocLinkBase = `${ELASTIC_WEBSITE_URL}guide/en/`; - -const esBase = `${elasticDocLinkBase}elasticsearch/reference/${DOC_LINK_VERSION}`; -const esStackBase = `${elasticDocLinkBase}elastic-stack-overview/${DOC_LINK_VERSION}`; -const kibanaBase = `${elasticDocLinkBase}kibana/${DOC_LINK_VERSION}`; - -export const putWatchApiUrl = `${esBase}/watcher-api-put-watch.html`; -export const executeWatchApiUrl = `${esBase}/watcher-api-execute-watch.html#watcher-api-execute-watch-action-mode`; -export const watcherGettingStartedUrl = `${kibanaBase}/watcher-ui.html`; - -export const watchActionsConfigurationMap = { - [ACTION_TYPES.SLACK]: `${esStackBase}/actions-slack.html#configuring-slack`, - [ACTION_TYPES.PAGERDUTY]: `${esStackBase}/actions-pagerduty.html#configuring-pagerduty`, - [ACTION_TYPES.JIRA]: `${esStackBase}/actions-jira.html#configuring-jira`, -}; diff --git a/x-pack/legacy/plugins/watcher/public/lib/manage_angular_lifecycle.js b/x-pack/legacy/plugins/watcher/public/manage_angular_lifecycle.ts similarity index 75% rename from x-pack/legacy/plugins/watcher/public/lib/manage_angular_lifecycle.js rename to x-pack/legacy/plugins/watcher/public/manage_angular_lifecycle.ts index 3813e632a0a7..efd40eaf83da 100644 --- a/x-pack/legacy/plugins/watcher/public/lib/manage_angular_lifecycle.js +++ b/x-pack/legacy/plugins/watcher/public/manage_angular_lifecycle.ts @@ -6,7 +6,7 @@ import { unmountComponentAtNode } from 'react-dom'; -export const manageAngularLifecycle = ($scope, $route, elem) => { +export const manageAngularLifecycle = ($scope: any, $route: any, elem: HTMLElement) => { const lastRoute = $route.current; const deregister = $scope.$on('$locationChangeSuccess', () => { @@ -17,7 +17,12 @@ export const manageAngularLifecycle = ($scope, $route, elem) => { }); $scope.$on('$destroy', () => { - deregister && deregister(); - elem && unmountComponentAtNode(elem); + if (deregister) { + deregister(); + } + + if (elem) { + unmountComponentAtNode(elem); + } }); }; diff --git a/x-pack/legacy/plugins/watcher/public/models/index.d.ts b/x-pack/legacy/plugins/watcher/public/models/index.d.ts deleted file mode 100644 index d96d8d192e16..000000000000 --- a/x-pack/legacy/plugins/watcher/public/models/index.d.ts +++ /dev/null @@ -1,42 +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. - */ -declare module 'plugins/watcher/models/visualize_options' { - export const VisualizeOptions: any; -} - -declare module 'plugins/watcher/models/watch' { - export const Watch: any; -} - -declare module 'plugins/watcher/models/watch/threshold_watch' { - export const ThresholdWatch: any; -} - -declare module 'plugins/watcher/models/watch/json_watch' { - export const JsonWatch: any; -} - -declare module 'plugins/watcher/models/execute_details/execute_details' { - export const ExecuteDetails: any; -} - -declare module 'plugins/watcher/models/watch_history_item' { - export const WatchHistoryItem: any; -} - -declare module 'plugins/watcher/models/watch_status' { - export const WatchStatus: any; -} - -declare module 'plugins/watcher/models/settings' { - export const Settings: any; -} -declare module 'plugins/watcher/models/action' { - export const Action: any; -} -declare module 'ui/time_buckets' { - export const TimeBuckets: any; -} diff --git a/x-pack/legacy/plugins/watcher/public/app.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/app.tsx similarity index 60% rename from x-pack/legacy/plugins/watcher/public/app.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/app.tsx index b20634854796..36fa1cce9d6d 100644 --- a/x-pack/legacy/plugins/watcher/public/app.js +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/app.tsx @@ -4,54 +4,61 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import { HashRouter, Switch, Route, Redirect } from 'react-router-dom'; +import React from 'react'; +import { + ChromeStart, + DocLinksStart, + HttpSetup, + ToastsSetup, + IUiSettingsClient, +} from 'src/core/public'; + +import { EuiCallOut, EuiLink } from '@elastic/eui'; +import { + HashRouter, + Switch, + Route, + Redirect, + withRouter, + RouteComponentProps, +} from 'react-router-dom'; import { FormattedMessage } from '@kbn/i18n/react'; import { WatchStatus } from './sections/watch_status/components/watch_status'; import { WatchEdit } from './sections/watch_edit/components/watch_edit'; import { WatchList } from './sections/watch_list/components/watch_list'; import { registerRouter } from './lib/navigation'; import { BASE_PATH } from './constants'; -import { LICENSE_STATUS_VALID } from '../../../common/constants'; -import { EuiCallOut, EuiLink } from '@elastic/eui'; +import { LICENSE_STATUS_VALID } from '../../../../../common/constants'; +import { AppContextProvider } from './app_context'; +import { LegacyDependencies } from '../types'; -class ShareRouter extends Component { - static contextTypes = { - router: PropTypes.shape({ - history: PropTypes.shape({ - push: PropTypes.func.isRequired, - createHref: PropTypes.func.isRequired - }).isRequired - }).isRequired - } - constructor(...args) { - super(...args); - this.registerRouter(); - } +const ShareRouter = withRouter(({ children, history }: RouteComponentProps & { children: any }) => { + registerRouter({ history }); + return children; +}); - registerRouter() { - // Share the router with the app without requiring React or context. - const { router } = this.context; - registerRouter(router); - } - - render() { - return this.props.children; - } +export interface AppDeps { + chrome: ChromeStart; + docLinks: DocLinksStart; + toasts: ToastsSetup; + http: HttpSetup; + uiSettings: IUiSettingsClient; + legacy: LegacyDependencies; + euiUtils: any; } -export const App = ({ licenseStatus }) => { - const { status, message } = licenseStatus; + +export const App = (deps: AppDeps) => { + const { status, message } = deps.legacy.licenseStatus; if (status !== LICENSE_STATUS_VALID) { return ( - )} + } color="warning" iconType="help" > @@ -69,7 +76,9 @@ export const App = ({ licenseStatus }) => { return ( - + + + ); @@ -81,7 +90,11 @@ export const AppWithoutRouter = () => ( - + ); diff --git a/x-pack/legacy/plugins/watcher/public/np_ready/application/app_context.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/app_context.tsx new file mode 100644 index 000000000000..5696ab3cb91b --- /dev/null +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/app_context.tsx @@ -0,0 +1,65 @@ +/* + * 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, { createContext, useContext } from 'react'; +import { DocLinksStart } from 'src/core/public'; +import { ACTION_TYPES } from '../../../common/constants'; +import { AppDeps } from './app'; + +interface ContextValue extends Omit { + links: ReturnType; +} + +const AppContext = createContext(null as any); + +const generateDocLinks = ({ ELASTIC_WEBSITE_URL, DOC_LINK_VERSION }: DocLinksStart) => { + const elasticDocLinkBase = `${ELASTIC_WEBSITE_URL}guide/en/`; + const esBase = `${elasticDocLinkBase}elasticsearch/reference/${DOC_LINK_VERSION}`; + const kibanaBase = `${elasticDocLinkBase}kibana/${DOC_LINK_VERSION}`; + const putWatchApiUrl = `${esBase}/watcher-api-put-watch.html`; + const executeWatchApiUrl = `${esBase}/watcher-api-execute-watch.html#watcher-api-execute-watch-action-mode`; + const watcherGettingStartedUrl = `${kibanaBase}/watcher-ui.html`; + const watchActionsConfigurationMap = { + [ACTION_TYPES.SLACK]: `${esBase}/actions-slack.html#configuring-slack`, + [ACTION_TYPES.PAGERDUTY]: `${esBase}/actions-pagerduty.html#configuring-pagerduty`, + [ACTION_TYPES.JIRA]: `${esBase}/actions-jira.html#configuring-jira`, + }; + + return { + putWatchApiUrl, + executeWatchApiUrl, + watcherGettingStartedUrl, + watchActionsConfigurationMap, + }; +}; + +export const AppContextProvider = ({ + children, + value, +}: { + value: AppDeps; + children: React.ReactNode; +}) => { + const { docLinks, ...rest } = value; + return ( + + {children} + + ); +}; + +export const useAppContext = () => { + const ctx = useContext(AppContext); + if (!ctx) { + throw new Error('"useAppContext" can only be called inside of AppContext.Provider!'); + } + return ctx; +}; diff --git a/x-pack/legacy/plugins/watcher/public/np_ready/application/boot.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/boot.tsx new file mode 100644 index 000000000000..3f2a10f00464 --- /dev/null +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/boot.tsx @@ -0,0 +1,35 @@ +/* + * 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 { render, unmountComponentAtNode } from 'react-dom'; +import { SavedObjectsClientContract } from 'src/core/public'; + +import { App, AppDeps } from './app'; +import { setHttpClient, setSavedObjectsClient } from './lib/api'; +import { LegacyDependencies } from '../types'; + +interface BootDeps extends AppDeps { + element: HTMLElement; + savedObjects: SavedObjectsClientContract; + I18nContext: any; + legacy: LegacyDependencies; +} + +export const boot = (bootDeps: BootDeps) => { + const { I18nContext, element, legacy, savedObjects, ...appDeps } = bootDeps; + + setHttpClient(appDeps.http); + setSavedObjectsClient(savedObjects); + + render( + + + , + element + ); + return () => unmountComponentAtNode(element); +}; diff --git a/x-pack/legacy/plugins/watcher/public/components/confirm_watches_modal.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/components/confirm_watches_modal.tsx similarity index 100% rename from x-pack/legacy/plugins/watcher/public/components/confirm_watches_modal.tsx rename to x-pack/legacy/plugins/watcher/public/np_ready/application/components/confirm_watches_modal.tsx diff --git a/x-pack/legacy/plugins/watcher/public/components/delete_watches_modal.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/components/delete_watches_modal.tsx similarity index 95% rename from x-pack/legacy/plugins/watcher/public/components/delete_watches_modal.tsx rename to x-pack/legacy/plugins/watcher/public/np_ready/application/components/delete_watches_modal.tsx index 6d75495cbfc2..363185f3457d 100644 --- a/x-pack/legacy/plugins/watcher/public/components/delete_watches_modal.tsx +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/components/delete_watches_modal.tsx @@ -6,8 +6,8 @@ import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; -import { toastNotifications } from 'ui/notify'; import { deleteWatches } from '../lib/api'; +import { useAppContext } from '../app_context'; export const DeleteWatchesModal = ({ watchesToDelete, @@ -16,6 +16,7 @@ export const DeleteWatchesModal = ({ watchesToDelete: string[]; callback: (deleted?: string[]) => void; }) => { + const { toasts } = useAppContext(); const numWatchesToDelete = watchesToDelete.length; if (!numWatchesToDelete) { return null; @@ -54,7 +55,7 @@ export const DeleteWatchesModal = ({ const numErrors = errors.length; callback(successes); if (numSuccesses > 0) { - toastNotifications.addSuccess( + toasts.addSuccess( i18n.translate( 'xpack.watcher.sections.watchList.deleteSelectedWatchesSuccessNotification.descriptionText', { @@ -67,7 +68,7 @@ export const DeleteWatchesModal = ({ } if (numErrors > 0) { - toastNotifications.addDanger( + toasts.addDanger( i18n.translate( 'xpack.watcher.sections.watchList.deleteSelectedWatchesErrorNotification.descriptionText', { diff --git a/x-pack/legacy/plugins/watcher/public/components/form_errors.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/components/form_errors.tsx similarity index 100% rename from x-pack/legacy/plugins/watcher/public/components/form_errors.tsx rename to x-pack/legacy/plugins/watcher/public/np_ready/application/components/form_errors.tsx diff --git a/x-pack/legacy/plugins/watcher/public/components/index.ts b/x-pack/legacy/plugins/watcher/public/np_ready/application/components/index.ts similarity index 100% rename from x-pack/legacy/plugins/watcher/public/components/index.ts rename to x-pack/legacy/plugins/watcher/public/np_ready/application/components/index.ts diff --git a/x-pack/legacy/plugins/watcher/public/components/page_error/index.ts b/x-pack/legacy/plugins/watcher/public/np_ready/application/components/page_error/index.ts similarity index 100% rename from x-pack/legacy/plugins/watcher/public/components/page_error/index.ts rename to x-pack/legacy/plugins/watcher/public/np_ready/application/components/page_error/index.ts diff --git a/x-pack/legacy/plugins/watcher/public/components/page_error/page_error.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/components/page_error/page_error.tsx similarity index 100% rename from x-pack/legacy/plugins/watcher/public/components/page_error/page_error.tsx rename to x-pack/legacy/plugins/watcher/public/np_ready/application/components/page_error/page_error.tsx diff --git a/x-pack/legacy/plugins/watcher/public/components/page_error/page_error_forbidden.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/components/page_error/page_error_forbidden.tsx similarity index 100% rename from x-pack/legacy/plugins/watcher/public/components/page_error/page_error_forbidden.tsx rename to x-pack/legacy/plugins/watcher/public/np_ready/application/components/page_error/page_error_forbidden.tsx diff --git a/x-pack/legacy/plugins/watcher/public/components/page_error/page_error_not_exist.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/components/page_error/page_error_not_exist.tsx similarity index 100% rename from x-pack/legacy/plugins/watcher/public/components/page_error/page_error_not_exist.tsx rename to x-pack/legacy/plugins/watcher/public/np_ready/application/components/page_error/page_error_not_exist.tsx diff --git a/x-pack/legacy/plugins/watcher/public/components/section_error.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/components/section_error.tsx similarity index 80% rename from x-pack/legacy/plugins/watcher/public/components/section_error.tsx rename to x-pack/legacy/plugins/watcher/public/np_ready/application/components/section_error.tsx index 8951b95b7507..1c77cf2b49ae 100644 --- a/x-pack/legacy/plugins/watcher/public/components/section_error.tsx +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/components/section_error.tsx @@ -8,6 +8,18 @@ import { EuiCallOut, EuiSpacer } from '@elastic/eui'; import React, { Fragment } from 'react'; export interface Error { + error: string; + + /** + * wrapEsError() on the server adds a "cause" array + */ + cause?: string[]; + + message?: string; + + /** + * @deprecated + */ data: { error: string; cause?: string[]; @@ -21,11 +33,9 @@ interface Props { } export const SectionError: React.FunctionComponent = ({ title, error, ...rest }) => { - const { - error: errorString, - cause, // wrapEsError() on the server adds a "cause" array - message, - } = error.data; + const data = error.data || error; + + const { error: errorString, cause, message } = data; return ( diff --git a/x-pack/legacy/plugins/watcher/public/components/section_loading.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/components/section_loading.tsx similarity index 100% rename from x-pack/legacy/plugins/watcher/public/components/section_loading.tsx rename to x-pack/legacy/plugins/watcher/public/np_ready/application/components/section_loading.tsx diff --git a/x-pack/legacy/plugins/watcher/public/components/watch_status.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/components/watch_status.tsx similarity index 95% rename from x-pack/legacy/plugins/watcher/public/components/watch_status.tsx rename to x-pack/legacy/plugins/watcher/public/np_ready/application/components/watch_status.tsx index 39e6a5247b4a..8afd174f8561 100644 --- a/x-pack/legacy/plugins/watcher/public/components/watch_status.tsx +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/components/watch_status.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { EuiIcon, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; -import { ACTION_STATES, WATCH_STATES } from '../../common/constants'; +import { ACTION_STATES, WATCH_STATES } from '../../../../common/constants'; function StatusIcon({ status }: { status: string }) { switch (status) { diff --git a/x-pack/legacy/plugins/watcher/public/constants/base_path.ts b/x-pack/legacy/plugins/watcher/public/np_ready/application/constants/base_path.ts similarity index 100% rename from x-pack/legacy/plugins/watcher/public/constants/base_path.ts rename to x-pack/legacy/plugins/watcher/public/np_ready/application/constants/base_path.ts diff --git a/x-pack/legacy/plugins/watcher/public/constants/index.ts b/x-pack/legacy/plugins/watcher/public/np_ready/application/constants/index.ts similarity index 100% rename from x-pack/legacy/plugins/watcher/public/constants/index.ts rename to x-pack/legacy/plugins/watcher/public/np_ready/application/constants/index.ts diff --git a/x-pack/legacy/plugins/watcher/public/index.scss b/x-pack/legacy/plugins/watcher/public/np_ready/application/index.scss similarity index 100% rename from x-pack/legacy/plugins/watcher/public/index.scss rename to x-pack/legacy/plugins/watcher/public/np_ready/application/index.scss diff --git a/x-pack/legacy/plugins/watcher/public/lib/api.ts b/x-pack/legacy/plugins/watcher/public/np_ready/application/lib/api.ts similarity index 61% rename from x-pack/legacy/plugins/watcher/public/lib/api.ts rename to x-pack/legacy/plugins/watcher/public/np_ready/application/lib/api.ts index d5c430f9244c..c08545904e35 100644 --- a/x-pack/legacy/plugins/watcher/public/lib/api.ts +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/lib/api.ts @@ -3,20 +3,20 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { Settings } from 'plugins/watcher/models/settings'; -import { Watch } from 'plugins/watcher/models/watch'; -import { WatchHistoryItem } from 'plugins/watcher/models/watch_history_item'; -import { WatchStatus } from 'plugins/watcher/models/watch_status'; - -import { __await } from 'tslib'; -import chrome from 'ui/chrome'; -import { ROUTES } from '../../common/constants'; -import { BaseWatch, ExecutedWatchDetails } from '../../common/types/watch_types'; +import { HttpSetup, SavedObjectsClientContract } from 'src/core/public'; +import { Settings } from 'plugins/watcher/np_ready/application/models/settings'; +import { Watch } from 'plugins/watcher/np_ready/application/models/watch'; +import { WatchHistoryItem } from 'plugins/watcher/np_ready/application/models/watch_history_item'; +import { WatchStatus } from 'plugins/watcher/np_ready/application/models/watch_status'; + +import { BaseWatch, ExecutedWatchDetails } from '../../../../common/types/watch_types'; import { useRequest, sendRequest } from './use_request'; -let httpClient: ng.IHttpService; +import { ROUTES } from '../../../../common/constants'; + +let httpClient: HttpSetup; -export const setHttpClient = (anHttpClient: ng.IHttpService) => { +export const setHttpClient = (anHttpClient: HttpSetup) => { httpClient = anHttpClient; }; @@ -24,19 +24,17 @@ export const getHttpClient = () => { return httpClient; }; -let savedObjectsClient: any; +let savedObjectsClient: SavedObjectsClientContract; -export const setSavedObjectsClient = (aSavedObjectsClient: any) => { +export const setSavedObjectsClient = (aSavedObjectsClient: SavedObjectsClientContract) => { savedObjectsClient = aSavedObjectsClient; }; -export const getSavedObjectsClient = () => { - return savedObjectsClient; -}; +export const getSavedObjectsClient = () => savedObjectsClient; -const basePath = chrome.addBasePath(ROUTES.API_ROOT); +const basePath = ROUTES.API_ROOT; -export const loadWatches = (pollIntervalMs: number) => { +export const useLoadWatches = (pollIntervalMs: number) => { return useRequest({ path: `${basePath}/watches`, method: 'get', @@ -47,7 +45,7 @@ export const loadWatches = (pollIntervalMs: number) => { }); }; -export const loadWatchDetail = (id: string) => { +export const useLoadWatchDetail = (id: string) => { return useRequest({ path: `${basePath}/watch/${id}`, method: 'get', @@ -55,15 +53,10 @@ export const loadWatchDetail = (id: string) => { }); }; -export const loadWatchHistory = (id: string, startTime: string) => { - let path = `${basePath}/watch/${id}/history`; - - if (startTime) { - path += `?startTime=${startTime}`; - } - +export const useLoadWatchHistory = (id: string, startTime: string) => { return useRequest({ - path, + query: startTime ? { startTime } : undefined, + path: `${basePath}/watch/${id}/history`, method: 'get', deserializer: ({ watchHistoryItems = [] }: { watchHistoryItems: any }) => { return watchHistoryItems.map((historyItem: any) => @@ -73,7 +66,7 @@ export const loadWatchHistory = (id: string, startTime: string) => { }); }; -export const loadWatchHistoryDetail = (id: string | undefined) => { +export const useLoadWatchHistoryDetail = (id: string | undefined) => { return useRequest({ path: !id ? '' : `${basePath}/history/${id}`, method: 'get', @@ -83,12 +76,10 @@ export const loadWatchHistoryDetail = (id: string | undefined) => { }; export const deleteWatches = async (watchIds: string[]) => { - const body = { + const body = JSON.stringify({ watchIds, - }; - const { - data: { results }, - } = await getHttpClient().post(`${basePath}/watches/delete`, body); + }); + const { results } = await getHttpClient().post(`${basePath}/watches/delete`, { body }); return results; }; @@ -107,8 +98,8 @@ export const activateWatch = async (id: string) => { }; export const loadWatch = async (id: string) => { - const { data: watch } = await getHttpClient().get(`${basePath}/watch/${id}`); - return Watch.fromUpstreamJson(watch.watch); + const { watch } = await getHttpClient().get(`${basePath}/watch/${id}`); + return Watch.fromUpstreamJson(watch); }; export const getMatchingIndices = async (pattern: string) => { @@ -118,32 +109,32 @@ export const getMatchingIndices = async (pattern: string) => { if (!pattern.endsWith('*')) { pattern = `${pattern}*`; } - const { - data: { indices }, - } = await getHttpClient().post(`${basePath}/indices`, { pattern }); + const body = JSON.stringify({ pattern }); + const { indices } = await getHttpClient().post(`${basePath}/indices`, { body }); return indices; }; export const fetchFields = async (indexes: string[]) => { - const { - data: { fields }, - } = await getHttpClient().post(`${basePath}/fields`, { indexes }); + const { fields } = await getHttpClient().post(`${basePath}/fields`, { + body: JSON.stringify({ indexes }), + }); return fields; }; export const createWatch = async (watch: BaseWatch) => { - const { data } = await getHttpClient().put(`${basePath}/watch/${watch.id}`, watch.upstreamJson); - return data; + return await getHttpClient().put(`${basePath}/watch/${watch.id}`, { + body: JSON.stringify(watch.upstreamJson), + }); }; export const executeWatch = async (executeWatchDetails: ExecutedWatchDetails, watch: BaseWatch) => { return sendRequest({ path: `${basePath}/watch/execute`, method: 'put', - body: { + body: JSON.stringify({ executeDetails: executeWatchDetails.upstreamJson, watch: watch.upstreamJson, - }, + }), }); }; @@ -156,19 +147,19 @@ export const loadIndexPatterns = async () => { return savedObjects; }; -export const getWatchVisualizationData = (watchModel: BaseWatch, visualizeOptions: any) => { +export const useGetWatchVisualizationData = (watchModel: BaseWatch, visualizeOptions: any) => { return useRequest({ path: `${basePath}/watch/visualize`, method: 'post', - body: { + body: JSON.stringify({ watch: watchModel.upstreamJson, options: visualizeOptions.upstreamJson, - }, + }), deserializer: ({ visualizeData }: { visualizeData: any }) => visualizeData, }); }; -export const loadSettings = () => { +export const useLoadSettings = () => { return useRequest({ path: `${basePath}/settings`, method: 'get', @@ -183,11 +174,8 @@ export const loadSettings = () => { }; export const ackWatchAction = async (watchId: string, actionId: string) => { - const { - data: { watchStatus }, - } = await getHttpClient().put( - `${basePath}/watch/${watchId}/action/${actionId}/acknowledge`, - null + const { watchStatus } = await getHttpClient().put( + `${basePath}/watch/${watchId}/action/${actionId}/acknowledge` ); return WatchStatus.fromUpstreamJson(watchStatus); }; diff --git a/x-pack/legacy/plugins/watcher/public/lib/breadcrumbs.ts b/x-pack/legacy/plugins/watcher/public/np_ready/application/lib/breadcrumbs.ts similarity index 100% rename from x-pack/legacy/plugins/watcher/public/lib/breadcrumbs.ts rename to x-pack/legacy/plugins/watcher/public/np_ready/application/lib/breadcrumbs.ts diff --git a/x-pack/legacy/plugins/watcher/public/lib/format_date.ts b/x-pack/legacy/plugins/watcher/public/np_ready/application/lib/format_date.ts similarity index 100% rename from x-pack/legacy/plugins/watcher/public/lib/format_date.ts rename to x-pack/legacy/plugins/watcher/public/np_ready/application/lib/format_date.ts diff --git a/x-pack/legacy/plugins/watcher/public/lib/get_search_value.ts b/x-pack/legacy/plugins/watcher/public/np_ready/application/lib/get_search_value.ts similarity index 100% rename from x-pack/legacy/plugins/watcher/public/lib/get_search_value.ts rename to x-pack/legacy/plugins/watcher/public/np_ready/application/lib/get_search_value.ts diff --git a/x-pack/legacy/plugins/watcher/public/lib/get_time_unit_label.ts b/x-pack/legacy/plugins/watcher/public/np_ready/application/lib/get_time_unit_label.ts similarity index 95% rename from x-pack/legacy/plugins/watcher/public/lib/get_time_unit_label.ts rename to x-pack/legacy/plugins/watcher/public/np_ready/application/lib/get_time_unit_label.ts index 35bd19e7007c..ce3b96ac17de 100644 --- a/x-pack/legacy/plugins/watcher/public/lib/get_time_unit_label.ts +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/lib/get_time_unit_label.ts @@ -5,7 +5,7 @@ */ import { i18n } from '@kbn/i18n'; -import { TIME_UNITS } from '../../common/constants'; +import { TIME_UNITS } from '../../../../common/constants'; export function getTimeUnitLabel(timeUnit = TIME_UNITS.SECOND, timeValue = '0') { switch (timeUnit) { diff --git a/x-pack/legacy/plugins/watcher/public/lib/navigation.ts b/x-pack/legacy/plugins/watcher/public/np_ready/application/lib/navigation.ts similarity index 100% rename from x-pack/legacy/plugins/watcher/public/lib/navigation.ts rename to x-pack/legacy/plugins/watcher/public/np_ready/application/lib/navigation.ts diff --git a/x-pack/legacy/plugins/watcher/public/lib/use_request.ts b/x-pack/legacy/plugins/watcher/public/np_ready/application/lib/use_request.ts similarity index 99% rename from x-pack/legacy/plugins/watcher/public/lib/use_request.ts rename to x-pack/legacy/plugins/watcher/public/np_ready/application/lib/use_request.ts index 4788b655d9e8..572403b14b9d 100644 --- a/x-pack/legacy/plugins/watcher/public/lib/use_request.ts +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/lib/use_request.ts @@ -11,6 +11,7 @@ import { sendRequest as _sendRequest, useRequest as _useRequest, } from '../shared_imports'; + import { getHttpClient } from './api'; export const sendRequest = (config: SendRequestConfig): Promise => { diff --git a/x-pack/legacy/plugins/watcher/public/models/action/action.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/action/action.js similarity index 95% rename from x-pack/legacy/plugins/watcher/public/models/action/action.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/action/action.js index 2f1850c3a434..4e6ec21703b9 100644 --- a/x-pack/legacy/plugins/watcher/public/models/action/action.js +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/action/action.js @@ -5,7 +5,7 @@ */ import { get, set } from 'lodash'; -import { ACTION_TYPES } from '../../../common/constants'; +import { ACTION_TYPES } from '../../../../../common/constants'; import { EmailAction } from './email_action'; import { LoggingAction } from './logging_action'; import { SlackAction } from './slack_action'; diff --git a/x-pack/legacy/plugins/watcher/public/models/action/base_action.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/action/base_action.js similarity index 100% rename from x-pack/legacy/plugins/watcher/public/models/action/base_action.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/action/base_action.js diff --git a/x-pack/legacy/plugins/watcher/public/models/action/email_action.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/action/email_action.js similarity index 100% rename from x-pack/legacy/plugins/watcher/public/models/action/email_action.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/action/email_action.js diff --git a/x-pack/legacy/plugins/watcher/public/models/action/index.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/action/index.js similarity index 100% rename from x-pack/legacy/plugins/watcher/public/models/action/index.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/action/index.js diff --git a/x-pack/legacy/plugins/watcher/public/models/action/index_action.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/action/index_action.js similarity index 100% rename from x-pack/legacy/plugins/watcher/public/models/action/index_action.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/action/index_action.js diff --git a/x-pack/legacy/plugins/watcher/public/models/action/jira_action.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/action/jira_action.js similarity index 100% rename from x-pack/legacy/plugins/watcher/public/models/action/jira_action.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/action/jira_action.js diff --git a/x-pack/legacy/plugins/watcher/public/models/action/logging_action.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/action/logging_action.js similarity index 100% rename from x-pack/legacy/plugins/watcher/public/models/action/logging_action.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/action/logging_action.js diff --git a/x-pack/legacy/plugins/watcher/public/models/action/pagerduty_action.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/action/pagerduty_action.js similarity index 100% rename from x-pack/legacy/plugins/watcher/public/models/action/pagerduty_action.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/action/pagerduty_action.js diff --git a/x-pack/legacy/plugins/watcher/public/models/action/slack_action.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/action/slack_action.js similarity index 100% rename from x-pack/legacy/plugins/watcher/public/models/action/slack_action.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/action/slack_action.js diff --git a/x-pack/legacy/plugins/watcher/public/models/action/unknown_action.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/action/unknown_action.js similarity index 100% rename from x-pack/legacy/plugins/watcher/public/models/action/unknown_action.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/action/unknown_action.js diff --git a/x-pack/legacy/plugins/watcher/public/models/action/webhook_action.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/action/webhook_action.js similarity index 100% rename from x-pack/legacy/plugins/watcher/public/models/action/webhook_action.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/action/webhook_action.js diff --git a/x-pack/legacy/plugins/watcher/public/models/action_status/action_status.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/action_status/action_status.js similarity index 95% rename from x-pack/legacy/plugins/watcher/public/models/action_status/action_status.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/action_status/action_status.js index fa9e056554ab..b177eb5bb229 100644 --- a/x-pack/legacy/plugins/watcher/public/models/action_status/action_status.js +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/action_status/action_status.js @@ -5,7 +5,7 @@ */ import { get } from 'lodash'; -import { getMoment } from '../../../common/lib/get_moment'; +import { getMoment } from '../../../../../common/lib/get_moment'; export class ActionStatus { constructor(props = {}) { diff --git a/x-pack/legacy/plugins/watcher/public/models/action_status/index.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/action_status/index.js similarity index 100% rename from x-pack/legacy/plugins/watcher/public/models/action_status/index.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/action_status/index.js diff --git a/x-pack/legacy/plugins/watcher/public/models/execute_details/execute_details.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/execute_details/execute_details.js similarity index 100% rename from x-pack/legacy/plugins/watcher/public/models/execute_details/execute_details.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/execute_details/execute_details.js diff --git a/x-pack/legacy/plugins/watcher/public/models/execute_details/index.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/execute_details/index.js similarity index 100% rename from x-pack/legacy/plugins/watcher/public/models/execute_details/index.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/execute_details/index.js diff --git a/x-pack/legacy/plugins/watcher/public/np_ready/application/models/index.d.ts b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/index.d.ts new file mode 100644 index 000000000000..a8ddb6ca2b76 --- /dev/null +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/index.d.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. + */ +declare module 'plugins/watcher/np_ready/application/models/visualize_options' { + export const VisualizeOptions: any; +} + +declare module 'plugins/watcher/np_ready/application/models/watch' { + export const Watch: any; +} + +declare module 'plugins/watcher/np_ready/application/models/watch/threshold_watch' { + export const ThresholdWatch: any; +} + +declare module 'plugins/watcher/np_ready/application/models/watch/json_watch' { + export const JsonWatch: any; +} + +declare module 'plugins/watcher/np_ready/application/models/execute_details/execute_details' { + export const ExecuteDetails: any; +} + +declare module 'plugins/watcher/np_ready/application/models/watch_history_item' { + export const WatchHistoryItem: any; +} + +declare module 'plugins/watcher/np_ready/application/models/watch_status' { + export const WatchStatus: any; +} + +declare module 'plugins/watcher/np_ready/application/models/settings' { + export const Settings: any; +} +declare module 'plugins/watcher/np_ready/application/models/action' { + export const Action: any; +} diff --git a/x-pack/legacy/plugins/watcher/public/models/settings/index.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/settings/index.js similarity index 100% rename from x-pack/legacy/plugins/watcher/public/models/settings/index.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/settings/index.js diff --git a/x-pack/legacy/plugins/watcher/public/models/settings/settings.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/settings/settings.js similarity index 100% rename from x-pack/legacy/plugins/watcher/public/models/settings/settings.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/settings/settings.js diff --git a/x-pack/legacy/plugins/watcher/public/models/visualize_options/index.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/visualize_options/index.js similarity index 100% rename from x-pack/legacy/plugins/watcher/public/models/visualize_options/index.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/visualize_options/index.js diff --git a/x-pack/legacy/plugins/watcher/public/models/visualize_options/visualize_options.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/visualize_options/visualize_options.js similarity index 100% rename from x-pack/legacy/plugins/watcher/public/models/visualize_options/visualize_options.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/visualize_options/visualize_options.js diff --git a/x-pack/legacy/plugins/watcher/public/models/watch/agg_types.ts b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/agg_types.ts similarity index 94% rename from x-pack/legacy/plugins/watcher/public/models/watch/agg_types.ts rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/agg_types.ts index 65ab537889ea..cefaaa3b1abd 100644 --- a/x-pack/legacy/plugins/watcher/public/models/watch/agg_types.ts +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/agg_types.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AGG_TYPES } from '../../../common/constants'; +import { AGG_TYPES } from '../../../../../common/constants'; export interface AggType { text: string; diff --git a/x-pack/legacy/plugins/watcher/public/models/watch/base_watch.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/base_watch.js similarity index 100% rename from x-pack/legacy/plugins/watcher/public/models/watch/base_watch.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/base_watch.js diff --git a/x-pack/legacy/plugins/watcher/public/models/watch/comparators.ts b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/comparators.ts similarity index 96% rename from x-pack/legacy/plugins/watcher/public/models/watch/comparators.ts rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/comparators.ts index b636cdaf14c1..edc3a03c2522 100644 --- a/x-pack/legacy/plugins/watcher/public/models/watch/comparators.ts +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/comparators.ts @@ -6,7 +6,7 @@ import { i18n } from '@kbn/i18n'; -import { COMPARATORS } from '../../../common/constants'; +import { COMPARATORS } from '../../../../../common/constants'; export interface Comparator { text: string; diff --git a/x-pack/legacy/plugins/watcher/public/models/watch/default_watch.json b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/default_watch.json similarity index 100% rename from x-pack/legacy/plugins/watcher/public/models/watch/default_watch.json rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/default_watch.json diff --git a/x-pack/legacy/plugins/watcher/public/models/watch/group_by_types.ts b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/group_by_types.ts similarity index 100% rename from x-pack/legacy/plugins/watcher/public/models/watch/group_by_types.ts rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/group_by_types.ts diff --git a/x-pack/legacy/plugins/watcher/public/models/watch/index.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/index.js similarity index 100% rename from x-pack/legacy/plugins/watcher/public/models/watch/index.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/index.js diff --git a/x-pack/legacy/plugins/watcher/public/models/watch/json_watch.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/json_watch.js similarity index 98% rename from x-pack/legacy/plugins/watcher/public/models/watch/json_watch.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/json_watch.js index 3dd7af759970..2e2ee47640cf 100644 --- a/x-pack/legacy/plugins/watcher/public/models/watch/json_watch.js +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/json_watch.js @@ -7,7 +7,7 @@ import uuid from 'uuid'; import { get } from 'lodash'; import { BaseWatch } from './base_watch'; -import { ACTION_TYPES, WATCH_TYPES } from '../../../common/constants'; +import { ACTION_TYPES, WATCH_TYPES } from '../../../../../common/constants'; import defaultWatchJson from './default_watch.json'; import { i18n } from '@kbn/i18n'; diff --git a/x-pack/legacy/plugins/watcher/public/models/watch/lib/check_action_id_collision/check_action_id_collision.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/lib/check_action_id_collision/check_action_id_collision.js similarity index 100% rename from x-pack/legacy/plugins/watcher/public/models/watch/lib/check_action_id_collision/check_action_id_collision.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/lib/check_action_id_collision/check_action_id_collision.js diff --git a/x-pack/legacy/plugins/watcher/public/models/watch/lib/check_action_id_collision/index.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/lib/check_action_id_collision/index.js similarity index 100% rename from x-pack/legacy/plugins/watcher/public/models/watch/lib/check_action_id_collision/index.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/lib/check_action_id_collision/index.js diff --git a/x-pack/legacy/plugins/watcher/public/models/watch/lib/create_action_id/create_action_id.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/lib/create_action_id/create_action_id.js similarity index 100% rename from x-pack/legacy/plugins/watcher/public/models/watch/lib/create_action_id/create_action_id.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/lib/create_action_id/create_action_id.js diff --git a/x-pack/legacy/plugins/watcher/public/models/watch/lib/create_action_id/index.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/lib/create_action_id/index.js similarity index 100% rename from x-pack/legacy/plugins/watcher/public/models/watch/lib/create_action_id/index.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/lib/create_action_id/index.js diff --git a/x-pack/legacy/plugins/watcher/public/models/watch/monitoring_watch.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/monitoring_watch.js similarity index 92% rename from x-pack/legacy/plugins/watcher/public/models/watch/monitoring_watch.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/monitoring_watch.js index a0873934e175..3269fcbe459d 100644 --- a/x-pack/legacy/plugins/watcher/public/models/watch/monitoring_watch.js +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/monitoring_watch.js @@ -5,7 +5,7 @@ */ import { BaseWatch } from './base_watch'; -import { WATCH_TYPES } from '../../../common/constants'; +import { WATCH_TYPES } from '../../../../../common/constants'; /** * {@code MonitoringWatch} system defined watches created by the Monitoring plugin. diff --git a/x-pack/legacy/plugins/watcher/public/models/watch/threshold_watch.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/threshold_watch.js similarity index 99% rename from x-pack/legacy/plugins/watcher/public/models/watch/threshold_watch.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/threshold_watch.js index af995d6594a3..02fa99e7f3e1 100644 --- a/x-pack/legacy/plugins/watcher/public/models/watch/threshold_watch.js +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/threshold_watch.js @@ -6,7 +6,7 @@ import { BaseWatch } from './base_watch'; import uuid from 'uuid'; -import { WATCH_TYPES, SORT_ORDERS, COMPARATORS } from '../../../common/constants'; +import { WATCH_TYPES, SORT_ORDERS, COMPARATORS } from '../../../../../common/constants'; import { getTimeUnitLabel } from '../../lib/get_time_unit_label'; import { i18n } from '@kbn/i18n'; import { aggTypes } from './agg_types'; diff --git a/x-pack/legacy/plugins/watcher/public/models/watch/watch.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/watch.js similarity index 93% rename from x-pack/legacy/plugins/watcher/public/models/watch/watch.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/watch.js index d58a7799c651..2723fed92067 100644 --- a/x-pack/legacy/plugins/watcher/public/models/watch/watch.js +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/watch.js @@ -5,7 +5,7 @@ */ import { get, set } from 'lodash'; -import { WATCH_TYPES } from '../../../common/constants'; +import { WATCH_TYPES } from '../../../../../common/constants'; import { JsonWatch } from './json_watch'; import { ThresholdWatch } from './threshold_watch'; import { MonitoringWatch } from './monitoring_watch'; diff --git a/x-pack/legacy/plugins/watcher/public/models/watch_errors/index.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch_errors/index.js similarity index 100% rename from x-pack/legacy/plugins/watcher/public/models/watch_errors/index.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch_errors/index.js diff --git a/x-pack/legacy/plugins/watcher/public/models/watch_errors/watch_errors.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch_errors/watch_errors.js similarity index 100% rename from x-pack/legacy/plugins/watcher/public/models/watch_errors/watch_errors.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch_errors/watch_errors.js diff --git a/x-pack/legacy/plugins/watcher/public/models/watch_history_item/index.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch_history_item/index.js similarity index 100% rename from x-pack/legacy/plugins/watcher/public/models/watch_history_item/index.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch_history_item/index.js diff --git a/x-pack/legacy/plugins/watcher/public/models/watch_history_item/watch_history_item.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch_history_item/watch_history_item.js similarity index 91% rename from x-pack/legacy/plugins/watcher/public/models/watch_history_item/watch_history_item.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch_history_item/watch_history_item.js index a5918cec2764..785f9d19b23d 100644 --- a/x-pack/legacy/plugins/watcher/public/models/watch_history_item/watch_history_item.js +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch_history_item/watch_history_item.js @@ -6,7 +6,7 @@ import 'moment-duration-format'; import { get } from 'lodash'; -import { getMoment } from '../../../common/lib/get_moment'; +import { getMoment } from '../../../../../common/lib/get_moment'; import { WatchStatus } from '../watch_status'; export class WatchHistoryItem { diff --git a/x-pack/legacy/plugins/watcher/public/models/watch_status/index.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch_status/index.js similarity index 100% rename from x-pack/legacy/plugins/watcher/public/models/watch_status/index.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch_status/index.js diff --git a/x-pack/legacy/plugins/watcher/public/models/watch_status/watch_status.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch_status/watch_status.js similarity index 94% rename from x-pack/legacy/plugins/watcher/public/models/watch_status/watch_status.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch_status/watch_status.js index f213032a93c2..77007ea19038 100644 --- a/x-pack/legacy/plugins/watcher/public/models/watch_status/watch_status.js +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch_status/watch_status.js @@ -5,7 +5,7 @@ */ import { get } from 'lodash'; -import { getMoment } from '../../../common/lib/get_moment'; +import { getMoment } from '../../../../../common/lib/get_moment'; import { ActionStatus } from '../action_status'; export class WatchStatus { diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/json_watch_edit/index.ts b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/json_watch_edit/index.ts similarity index 100% rename from x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/json_watch_edit/index.ts rename to x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/json_watch_edit/index.ts diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/json_watch_edit/json_watch_edit.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/json_watch_edit/json_watch_edit.tsx similarity index 92% rename from x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/json_watch_edit/json_watch_edit.tsx rename to x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/json_watch_edit/json_watch_edit.tsx index 9c4b16e301b3..010e430c0719 100644 --- a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/json_watch_edit/json_watch_edit.tsx +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/json_watch_edit/json_watch_edit.tsx @@ -16,10 +16,10 @@ import { EuiTitle, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { ExecuteDetails } from 'plugins/watcher/models/execute_details/execute_details'; -import { getActionType } from '../../../../../common/lib/get_action_type'; -import { BaseWatch, ExecutedWatchDetails } from '../../../../../common/types/watch_types'; -import { ACTION_MODES, TIME_UNITS } from '../../../../../common/constants'; +import { ExecuteDetails } from 'plugins/watcher/np_ready/application/models/execute_details/execute_details'; +import { getActionType } from '../../../../../../../common/lib/get_action_type'; +import { BaseWatch, ExecutedWatchDetails } from '../../../../../../../common/types/watch_types'; +import { ACTION_MODES, TIME_UNITS } from '../../../../../../../common/constants'; import { JsonWatchEditForm } from './json_watch_edit_form'; import { JsonWatchEditSimulate } from './json_watch_edit_simulate'; import { WatchContext } from '../../watch_context'; diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/json_watch_edit/json_watch_edit_form.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/json_watch_edit/json_watch_edit_form.tsx similarity index 94% rename from x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/json_watch_edit/json_watch_edit_form.tsx rename to x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/json_watch_edit/json_watch_edit_form.tsx index 02a54fc9b927..376aeb205b85 100644 --- a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/json_watch_edit/json_watch_edit_form.tsx +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/json_watch_edit/json_watch_edit_form.tsx @@ -20,15 +20,20 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { serializeJsonWatch } from '../../../../../common/lib/serialization'; -import { ErrableFormRow, SectionError } from '../../../../components'; -import { putWatchApiUrl } from '../../../../lib/documentation_links'; +import { serializeJsonWatch } from '../../../../../../../common/lib/serialization'; +import { ErrableFormRow, SectionError, Error as ServerError } from '../../../../components'; import { onWatchSave } from '../../watch_edit_actions'; import { WatchContext } from '../../watch_context'; import { goToWatchList } from '../../../../lib/navigation'; import { RequestFlyout } from '../request_flyout'; +import { useAppContext } from '../../../../app_context'; export const JsonWatchEditForm = () => { + const { + links: { putWatchApiUrl }, + toasts, + } = useAppContext(); + const { watch, setWatchProperty } = useContext(WatchContext); const { errors } = watch.validate(); @@ -37,9 +42,7 @@ export const JsonWatchEditForm = () => { const [validationError, setValidationError] = useState(null); const [isRequestVisible, setIsRequestVisible] = useState(false); - const [serverError, setServerError] = useState<{ - data: { nessage: string; error: string }; - } | null>(null); + const [serverError, setServerError] = useState(null); const [isSaving, setIsSaving] = useState(false); @@ -192,7 +195,7 @@ export const JsonWatchEditForm = () => { isDisabled={hasErrors} onClick={async () => { setIsSaving(true); - const savedWatch = await onWatchSave(watch); + const savedWatch = await onWatchSave(watch, toasts); if (savedWatch && savedWatch.error) { const { data } = savedWatch.error; setIsSaving(false); diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/json_watch_edit/json_watch_edit_simulate.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/json_watch_edit/json_watch_edit_simulate.tsx similarity index 96% rename from x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/json_watch_edit/json_watch_edit_simulate.tsx rename to x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/json_watch_edit/json_watch_edit_simulate.tsx index e57a875aa435..7c5de3d8e929 100644 --- a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/json_watch_edit/json_watch_edit_simulate.tsx +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/json_watch_edit/json_watch_edit_simulate.tsx @@ -24,19 +24,19 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { ExecuteDetails } from 'plugins/watcher/models/execute_details/execute_details'; -import { WatchHistoryItem } from 'plugins/watcher/models/watch_history_item'; -import { ACTION_MODES, TIME_UNITS } from '../../../../../common/constants'; +import { ExecuteDetails } from 'plugins/watcher/np_ready/application/models/execute_details/execute_details'; +import { WatchHistoryItem } from 'plugins/watcher/np_ready/application/models/watch_history_item'; +import { ACTION_MODES, TIME_UNITS } from '../../../../../../../common/constants'; import { ExecutedWatchDetails, ExecutedWatchResults, -} from '../../../../../common/types/watch_types'; +} from '../../../../../../../common/types/watch_types'; import { ErrableFormRow } from '../../../../components/form_errors'; import { executeWatch } from '../../../../lib/api'; -import { executeWatchApiUrl } from '../../../../lib/documentation_links'; import { WatchContext } from '../../watch_context'; import { JsonWatchEditSimulateResults } from './json_watch_edit_simulate_results'; import { getTimeUnitLabel } from '../../../../lib/get_time_unit_label'; +import { useAppContext } from '../../../../app_context'; const actionModeOptions = Object.keys(ACTION_MODES).map(mode => ({ text: ACTION_MODES[mode], @@ -70,6 +70,9 @@ export const JsonWatchEditSimulate = ({ type: string; }>; }) => { + const { + links: { executeWatchApiUrl }, + } = useAppContext(); const { watch } = useContext(WatchContext); // hooks diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/json_watch_edit/json_watch_edit_simulate_results.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/json_watch_edit/json_watch_edit_simulate_results.tsx similarity index 99% rename from x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/json_watch_edit/json_watch_edit_simulate_results.tsx rename to x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/json_watch_edit/json_watch_edit_simulate_results.tsx index 1b2b4ab935e8..4b630f5bc81b 100644 --- a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/json_watch_edit/json_watch_edit_simulate_results.tsx +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/json_watch_edit/json_watch_edit_simulate_results.tsx @@ -21,7 +21,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { ExecutedWatchDetails, ExecutedWatchResults, -} from '../../../../../common/types/watch_types'; +} from '../../../../../../../common/types/watch_types'; import { getTypeFromAction } from '../../watch_edit_actions'; import { WatchContext } from '../../watch_context'; import { WatchStatus, SectionError } from '../../../../components'; diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/monitoring_watch_edit/index.ts b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/monitoring_watch_edit/index.ts similarity index 100% rename from x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/monitoring_watch_edit/index.ts rename to x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/monitoring_watch_edit/index.ts diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/monitoring_watch_edit/monitoring_watch_edit.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/monitoring_watch_edit/monitoring_watch_edit.tsx similarity index 100% rename from x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/monitoring_watch_edit/monitoring_watch_edit.tsx rename to x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/monitoring_watch_edit/monitoring_watch_edit.tsx diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/request_flyout.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/request_flyout.tsx similarity index 100% rename from x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/request_flyout.tsx rename to x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/request_flyout.tsx diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/email_action_fields.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/action_fields/email_action_fields.tsx similarity index 97% rename from x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/email_action_fields.tsx rename to x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/action_fields/email_action_fields.tsx index aebe8baaee41..3e70e49f4235 100644 --- a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/email_action_fields.tsx +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/action_fields/email_action_fields.tsx @@ -8,7 +8,7 @@ import React, { Fragment } from 'react'; import { EuiComboBox, EuiFieldText, EuiFormRow, EuiTextArea } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { ErrableFormRow } from '../../../../../components/form_errors'; -import { EmailAction } from '../../../../../../common/types/action_types'; +import { EmailAction } from '../../../../../../../../common/types/action_types'; interface Props { action: EmailAction; diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/index.ts b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/action_fields/index.ts similarity index 100% rename from x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/index.ts rename to x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/action_fields/index.ts diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/index_action_fields.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/action_fields/index_action_fields.tsx similarity index 94% rename from x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/index_action_fields.tsx rename to x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/action_fields/index_action_fields.tsx index 1cafb08ca406..b7ab76d9890b 100644 --- a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/index_action_fields.tsx +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/action_fields/index_action_fields.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { EuiFieldText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { ErrableFormRow } from '../../../../../components/form_errors'; -import { IndexAction } from '../../../../../../common/types/action_types'; +import { IndexAction } from '../../../../../../../../common/types/action_types'; interface Props { action: IndexAction; diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/jira_action_fields.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/action_fields/jira_action_fields.tsx similarity index 97% rename from x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/jira_action_fields.tsx rename to x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/action_fields/jira_action_fields.tsx index b8bdeaff9082..c09b3c44fde6 100644 --- a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/jira_action_fields.tsx +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/action_fields/jira_action_fields.tsx @@ -8,7 +8,7 @@ import React, { Fragment } from 'react'; import { EuiFieldText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { ErrableFormRow } from '../../../../../components/form_errors'; -import { JiraAction } from '../../../../../../common/types/action_types'; +import { JiraAction } from '../../../../../../../../common/types/action_types'; interface Props { action: JiraAction; diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/logging_action_fields.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/action_fields/logging_action_fields.tsx similarity index 94% rename from x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/logging_action_fields.tsx rename to x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/action_fields/logging_action_fields.tsx index b70e504519ae..7da2a22ecd6c 100644 --- a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/logging_action_fields.tsx +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/action_fields/logging_action_fields.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { EuiFieldText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { ErrableFormRow } from '../../../../../components/form_errors'; -import { LoggingAction } from '../../../../../../common/types/action_types'; +import { LoggingAction } from '../../../../../../../../common/types/action_types'; interface Props { action: LoggingAction; diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/pagerduty_action_fields.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/action_fields/pagerduty_action_fields.tsx similarity index 95% rename from x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/pagerduty_action_fields.tsx rename to x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/action_fields/pagerduty_action_fields.tsx index b2b670bf6b91..3287bdefa08a 100644 --- a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/pagerduty_action_fields.tsx +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/action_fields/pagerduty_action_fields.tsx @@ -7,7 +7,7 @@ import React, { Fragment } from 'react'; import { EuiFieldText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { ErrableFormRow } from '../../../../../components/form_errors'; -import { PagerDutyAction } from '../../../../../../common/types/action_types'; +import { PagerDutyAction } from '../../../../../../../../common/types/action_types'; interface Props { action: PagerDutyAction; diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/slack_action_fields.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/action_fields/slack_action_fields.tsx similarity index 96% rename from x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/slack_action_fields.tsx rename to x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/action_fields/slack_action_fields.tsx index 7b5a598c97eb..a72cf232d8d0 100644 --- a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/slack_action_fields.tsx +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/action_fields/slack_action_fields.tsx @@ -6,7 +6,7 @@ import React, { Fragment } from 'react'; import { EuiComboBox, EuiTextArea, EuiFormRow } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { SlackAction } from '../../../../../../common/types/action_types'; +import { SlackAction } from '../../../../../../../../common/types/action_types'; interface Props { action: SlackAction; diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/webhook_action_fields.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/action_fields/webhook_action_fields.tsx similarity index 98% rename from x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/webhook_action_fields.tsx rename to x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/action_fields/webhook_action_fields.tsx index c3784e1ca551..bdc6f0bcbb71 100644 --- a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/webhook_action_fields.tsx +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/action_fields/webhook_action_fields.tsx @@ -18,7 +18,7 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { ErrableFormRow } from '../../../../../components/form_errors'; -import { WebhookAction } from '../../../../../../common/types/action_types'; +import { WebhookAction } from '../../../../../../../../common/types/action_types'; interface Props { action: WebhookAction; @@ -39,7 +39,7 @@ export const WebhookActionFields: React.FunctionComponent = ({ useEffect(() => { editAction({ key: 'contentType', value: 'application/json' }); // set content-type for threshold watch to json by default - }, []); + }, [editAction]); return ( diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/index.ts b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/index.ts similarity index 100% rename from x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/index.ts rename to x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/index.ts diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/threshold_watch_action_accordion.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/threshold_watch_action_accordion.tsx similarity index 91% rename from x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/threshold_watch_action_accordion.tsx rename to x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/threshold_watch_action_accordion.tsx index 8b72eb7f1945..4fca772a1821 100644 --- a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/threshold_watch_action_accordion.tsx +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/threshold_watch_action_accordion.tsx @@ -21,13 +21,12 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { ExecuteDetails } from 'plugins/watcher/models/execute_details/execute_details'; -import { Action } from 'plugins/watcher/models/action'; -import { toastNotifications } from 'ui/notify'; -import { WatchHistoryItem } from 'plugins/watcher/models/watch_history_item'; -import { ThresholdWatch } from 'plugins/watcher/models/watch/threshold_watch'; -import { ActionType } from '../../../../../common/types/action_types'; -import { ACTION_TYPES, ACTION_MODES } from '../../../../../common/constants'; +import { ExecuteDetails } from 'plugins/watcher/np_ready/application/models/execute_details/execute_details'; +import { Action } from 'plugins/watcher/np_ready/application/models/action'; +import { WatchHistoryItem } from 'plugins/watcher/np_ready/application/models/watch_history_item'; +import { ThresholdWatch } from 'plugins/watcher/np_ready/application/models/watch/threshold_watch'; +import { ActionType } from '../../../../../../../common/types/action_types'; +import { ACTION_TYPES, ACTION_MODES } from '../../../../../../../common/constants'; import { WatchContext } from '../../watch_context'; import { WebhookActionFields, @@ -39,8 +38,8 @@ import { JiraActionFields, } from './action_fields'; import { executeWatch } from '../../../../lib/api'; -import { watchActionsConfigurationMap } from '../../../../lib/documentation_links'; import { SectionError } from '../../../../components'; +import { useAppContext } from '../../../../app_context'; const actionFieldsComponentMap = { [ACTION_TYPES.LOGGING]: LoggingActionFields, @@ -71,6 +70,10 @@ export const WatchActionsAccordion: React.FunctionComponent = ({ settings, actionErrors, }) => { + const { + links: { watchActionsConfigurationMap }, + toasts, + } = useAppContext(); const { watch, setWatchProperty } = useContext(WatchContext); const { actions } = watch; @@ -238,9 +241,9 @@ export const WatchActionsAccordion: React.FunctionComponent = ({ if (actionStatus && actionStatus.lastExecutionSuccessful === false) { const message = actionStatus.lastExecutionReason || action.simulateFailMessage; - return toastNotifications.addDanger(message); + return toasts.addDanger(message); } - return toastNotifications.addSuccess(action.simulateMessage); + return toasts.addSuccess(action.simulateMessage); }} > {action.simulatePrompt} diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/threshold_watch_action_dropdown.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/threshold_watch_action_dropdown.tsx similarity index 96% rename from x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/threshold_watch_action_dropdown.tsx rename to x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/threshold_watch_action_dropdown.tsx index 82f3352b4e02..d92cccfa00f1 100644 --- a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/threshold_watch_action_dropdown.tsx +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/threshold_watch_action_dropdown.tsx @@ -16,9 +16,9 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React, { useContext, useState } from 'react'; -import { Action } from 'plugins/watcher/models/action'; +import { Action } from 'plugins/watcher/np_ready/application/models/action'; import { FormattedMessage } from '@kbn/i18n/react'; -import { ACTION_TYPES } from '../../../../../common/constants'; +import { ACTION_TYPES } from '../../../../../../../common/constants'; import { WatchContext } from '../../watch_context'; const disabledMessage = i18n.translate( diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/threshold_watch_action_panel.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/threshold_watch_action_panel.tsx similarity index 93% rename from x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/threshold_watch_action_panel.tsx rename to x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/threshold_watch_action_panel.tsx index a2e46652429e..6072f93e53cf 100644 --- a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/threshold_watch_action_panel.tsx +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/threshold_watch_action_panel.tsx @@ -6,7 +6,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiTitle, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React, { useContext } from 'react'; -import { loadSettings } from '../../../../lib/api'; +import { useLoadSettings } from '../../../../lib/api'; import { WatchActionsDropdown } from './threshold_watch_action_dropdown'; import { WatchActionsAccordion } from './threshold_watch_action_accordion'; import { WatchContext } from '../../watch_context'; @@ -22,7 +22,7 @@ interface Props { export const WatchActionsPanel: React.FunctionComponent = ({ actionErrors }) => { const { watch } = useContext(WatchContext); - const { data: settings, isLoading } = loadSettings(); + const { data: settings, isLoading } = useLoadSettings(); return (
diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/threshold_watch_edit.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/threshold_watch_edit.tsx similarity index 95% rename from x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/threshold_watch_edit.tsx rename to x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/threshold_watch_edit.tsx index 910d4f1e0b15..f1b5d2c9eab7 100644 --- a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/threshold_watch_edit.tsx +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/threshold_watch_edit.tsx @@ -26,9 +26,9 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { TIME_UNITS } from '../../../../../common/constants'; -import { serializeThresholdWatch } from '../../../../../common/lib/serialization'; -import { ErrableFormRow, SectionError } from '../../../../components'; +import { TIME_UNITS } from '../../../../../../../common/constants'; +import { serializeThresholdWatch } from '../../../../../../../common/lib/serialization'; +import { ErrableFormRow, SectionError, Error as ServerError } from '../../../../components'; import { fetchFields, getMatchingIndices, loadIndexPatterns } from '../../../../lib/api'; import { aggTypes } from '../../../../models/watch/agg_types'; import { groupByTypes } from '../../../../models/watch/group_by_types'; @@ -40,6 +40,7 @@ import { WatchActionsPanel } from './threshold_watch_action_panel'; import { getTimeUnitLabel } from '../../../../lib/get_time_unit_label'; import { goToWatchList } from '../../../../lib/navigation'; import { RequestFlyout } from '../request_flyout'; +import { useAppContext } from '../../../../app_context'; const expressionFieldsWithValidation = [ 'aggField', @@ -104,7 +105,7 @@ const getTimeFieldOptions = (fields: any) => { }; interface IOption { label: string; - options: Array<{ value: string; label: string }>; + options: Array<{ value: string; label: string; key?: string }>; } const getIndexOptions = async (patternString: string, indexPatterns: string[]) => { @@ -129,12 +130,14 @@ const getIndexOptions = async (patternString: string, indexPatterns: string[]) = defaultMessage: 'Based on your indices and index patterns', } ), - options: matchingOptions.map(match => { - return { - label: match, - value: match, - }; - }), + options: matchingOptions + .map(match => { + return { + label: match, + value: match, + }; + }) + .sort((a, b) => String(a.label).localeCompare(b.label)), }); } @@ -144,6 +147,7 @@ const getIndexOptions = async (patternString: string, indexPatterns: string[]) = }), options: [ { + key: 'UNIQUE_CHOOSE_KEY', value: patternString, label: patternString, }, @@ -155,7 +159,8 @@ const getIndexOptions = async (patternString: string, indexPatterns: string[]) = export const ThresholdWatchEdit = ({ pageTitle }: { pageTitle: string }) => { // hooks - const [indexPatterns, setIndexPatterns] = useState([]); + const { toasts } = useAppContext(); + const [indexPatterns, setIndexPatterns] = useState([]); const [esFields, setEsFields] = useState([]); const [indexOptions, setIndexOptions] = useState([]); const [timeFieldOptions, setTimeFieldOptions] = useState([firstFieldOption]); @@ -165,34 +170,33 @@ export const ThresholdWatchEdit = ({ pageTitle }: { pageTitle: string }) => { const [watchThresholdPopoverOpen, setWatchThresholdPopoverOpen] = useState(false); const [watchDurationPopoverOpen, setWatchDurationPopoverOpen] = useState(false); const [aggTypePopoverOpen, setAggTypePopoverOpen] = useState(false); - const [serverError, setServerError] = useState<{ - data: { nessage: string; error: string }; - } | null>(null); + const [serverError, setServerError] = useState(null); const [isSaving, setIsSaving] = useState(false); const [isIndiciesLoading, setIsIndiciesLoading] = useState(false); const [isRequestVisible, setIsRequestVisible] = useState(false); const { watch, setWatchProperty } = useContext(WatchContext); - const getIndexPatterns = async () => { - const indexPatternObjects = await loadIndexPatterns(); - const titles = indexPatternObjects.map((indexPattern: any) => indexPattern.attributes.title); - setIndexPatterns(titles); - }; + useEffect(() => { + const getIndexPatterns = async () => { + const indexPatternObjects = await loadIndexPatterns(); + const titles = indexPatternObjects.map((indexPattern: any) => indexPattern.attributes.title); + setIndexPatterns(titles); + }; - const loadData = async () => { - if (watch.index && watch.index.length > 0) { - const allEsFields = await getFields(watch.index); - const timeFields = getTimeFieldOptions(allEsFields); - setEsFields(allEsFields); - setTimeFieldOptions(timeFields); - setWatchProperty('timeFields', timeFields); - } - getIndexPatterns(); - }; + const loadData = async () => { + if (watch.index && watch.index.length > 0) { + const allEsFields = await getFields(watch.index); + const timeFields = getTimeFieldOptions(allEsFields); + setEsFields(allEsFields); + setTimeFieldOptions(timeFields); + setWatchProperty('timeFields', timeFields); + } + getIndexPatterns(); + }; - useEffect(() => { loadData(); + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const { errors } = watch.validate(); @@ -899,7 +903,7 @@ export const ThresholdWatchEdit = ({ pageTitle }: { pageTitle: string }) => { isLoading={isSaving} onClick={async () => { setIsSaving(true); - const savedWatch = await onWatchSave(watch); + const savedWatch = await onWatchSave(watch, toasts); if (savedWatch && savedWatch.error) { setIsSaving(false); return setServerError(savedWatch.error); diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/watch_visualization.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/watch_visualization.tsx similarity index 83% rename from x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/watch_visualization.tsx rename to x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/watch_visualization.tsx index 772f3cc024fe..a3da7d14c888 100644 --- a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/watch_visualization.tsx +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/watch_visualization.tsx @@ -18,20 +18,20 @@ import { ScaleType, Settings, } from '@elastic/charts'; -import { TimeBuckets } from 'ui/time_buckets'; import dateMath from '@elastic/datemath'; -import chrome from 'ui/chrome'; import moment from 'moment-timezone'; +import { IUiSettingsClient } from 'src/core/public'; import { EuiCallOut, EuiLoadingChart, EuiSpacer, EuiEmptyPrompt, EuiText } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { VisualizeOptions } from 'plugins/watcher/models/visualize_options'; -import { ThresholdWatch } from 'plugins/watcher/models/watch/threshold_watch'; -import { npStart } from 'ui/new_platform'; -import { getWatchVisualizationData } from '../../../../lib/api'; +import { VisualizeOptions } from 'plugins/watcher/np_ready/application/models/visualize_options'; +import { ThresholdWatch } from 'plugins/watcher/np_ready/application/models/watch/threshold_watch'; + +import { useGetWatchVisualizationData } from '../../../../lib/api'; import { WatchContext } from '../../watch_context'; import { aggTypes } from '../../../../models/watch/agg_types'; import { comparators } from '../../../../models/watch/comparators'; import { SectionError, Error } from '../../../../components'; +import { useAppContext } from '../../../../app_context'; const customTheme = () => { return { @@ -46,8 +46,7 @@ const customTheme = () => { }; }; -const getTimezone = () => { - const config = chrome.getUiSettingsClient(); +const getTimezone = (config: IUiSettingsClient) => { const DATE_FORMAT_CONFIG_KEY = 'dateFormat:tz'; const isCustomTimezone = !config.isDefault(DATE_FORMAT_CONFIG_KEY); if (isCustomTimezone) { @@ -59,8 +58,7 @@ const getTimezone = () => { return detectedTimezone; } // default to UTC if we can't figure out the timezone - const tzOffset = moment().format('Z'); - return tzOffset; + return moment().format('Z'); }; const getDomain = (watch: any) => { @@ -83,16 +81,20 @@ const getThreshold = (watch: any) => { return watch.threshold.slice(0, comparators[watch.thresholdComparator].requiredValues); }; -const getTimeBuckets = (watch: any) => { +const getTimeBuckets = (watch: any, timeBuckets: any) => { const domain = getDomain(watch); - const timeBuckets = new TimeBuckets(); timeBuckets.setBounds(domain); return timeBuckets; }; export const WatchVisualization = () => { + const { + legacy: { TimeBuckets }, + euiUtils, + uiSettings, + } = useAppContext(); const { watch } = useContext(WatchContext); - const chartsTheme = npStart.plugins.eui_utils.useChartsTheme(); + const chartsTheme = euiUtils.useChartsTheme(); const { index, timeField, @@ -117,7 +119,7 @@ export const WatchVisualization = () => { rangeFrom: domain.min, rangeTo: domain.max, interval, - timezone: getTimezone(), + timezone: getTimezone(uiSettings), }); // Fetching visualization data is independent of watch actions @@ -129,30 +131,33 @@ export const WatchVisualization = () => { data: watchVisualizationData, error, sendRequest: reload, - } = getWatchVisualizationData(watchWithoutActions, visualizeOptions); + } = useGetWatchVisualizationData(watchWithoutActions, visualizeOptions); - useEffect(() => { - // Prevent sending a second request on initial render. - if (isInitialRequest) { - return; - } - - reload(); - }, [ - index, - timeField, - triggerIntervalSize, - triggerIntervalUnit, - aggType, - aggField, - termSize, - termField, - thresholdComparator, - timeWindowSize, - timeWindowUnit, - groupBy, - threshold, - ]); + useEffect( + () => { + // Prevent sending a second request on initial render. + if (isInitialRequest) { + return; + } + reload(); + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [ + index, + timeField, + triggerIntervalSize, + triggerIntervalUnit, + aggType, + aggField, + termSize, + termField, + thresholdComparator, + timeWindowSize, + timeWindowUnit, + groupBy, + threshold, + ] + ); if (isInitialRequest && isLoading) { return ( @@ -190,7 +195,7 @@ export const WatchVisualization = () => { if (watchVisualizationData) { const watchVisualizationDataKeys = Object.keys(watchVisualizationData); - const timezone = getTimezone(); + const timezone = getTimezone(uiSettings); const actualThreshold = getThreshold(watch); let maxY = actualThreshold[actualThreshold.length - 1]; @@ -204,7 +209,7 @@ export const WatchVisualization = () => { const dateFormatter = (d: number) => { return moment(d) .tz(timezone) - .format(getTimeBuckets(watch).getScaledDateFormat()); + .format(getTimeBuckets(watch, new TimeBuckets()).getScaledDateFormat()); }; const aggLabel = aggTypes[watch.aggType].text; return ( diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/watch_edit.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/watch_edit.tsx similarity index 82% rename from x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/watch_edit.tsx rename to x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/watch_edit.tsx index 25daf190dc1b..9f252d3e542e 100644 --- a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/watch_edit.tsx +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/watch_edit.tsx @@ -9,13 +9,11 @@ import { isEqual } from 'lodash'; import { EuiPageContent } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import chrome from 'ui/chrome'; -import { MANAGEMENT_BREADCRUMB } from 'ui/management'; -import { Watch } from 'plugins/watcher/models/watch'; +import { Watch } from 'plugins/watcher/np_ready/application/models/watch'; import { FormattedMessage } from '@kbn/i18n/react'; -import { WATCH_TYPES } from '../../../../common/constants'; -import { BaseWatch } from '../../../../common/types/watch_types'; +import { WATCH_TYPES } from '../../../../../../common/constants'; +import { BaseWatch } from '../../../../../../common/types/watch_types'; import { getPageErrorCode, PageError, SectionLoading, SectionError } from '../../../components'; import { loadWatch } from '../../../lib/api'; import { listBreadcrumb, editBreadcrumb, createBreadcrumb } from '../../../lib/breadcrumbs'; @@ -23,6 +21,7 @@ import { JsonWatchEdit } from './json_watch_edit'; import { ThresholdWatchEdit } from './threshold_watch_edit'; import { MonitoringWatchEdit } from './monitoring_watch_edit'; import { WatchContext } from '../watch_context'; +import { useAppContext } from '../../../app_context'; const getTitle = (watch: BaseWatch) => { if (watch.isNew) { @@ -97,6 +96,10 @@ export const WatchEdit = ({ }; }) => { // hooks + const { + legacy: { MANAGEMENT_BREADCRUMB }, + chrome, + } = useAppContext(); const [{ watch, loadError }, dispatch] = useReducer(watchReducer, { watch: null }); const setWatchProperty = (property: string, value: any) => { @@ -107,33 +110,33 @@ export const WatchEdit = ({ dispatch({ command: 'addAction', payload: action }); }; - const getWatch = async () => { - if (id) { - try { - const loadedWatch = await loadWatch(id); - dispatch({ command: 'setWatch', payload: loadedWatch }); - } catch (error) { - dispatch({ command: 'setError', payload: error }); - } - } else if (type) { - const WatchType = Watch.getWatchTypes()[type]; - if (WatchType) { - dispatch({ command: 'setWatch', payload: new WatchType() }); + useEffect(() => { + const getWatch = async () => { + if (id) { + try { + const loadedWatch = await loadWatch(id); + dispatch({ command: 'setWatch', payload: loadedWatch }); + } catch (error) { + dispatch({ command: 'setError', payload: error }); + } + } else if (type) { + const WatchType = Watch.getWatchTypes()[type]; + if (WatchType) { + dispatch({ command: 'setWatch', payload: new WatchType() }); + } } - } - }; + }; - useEffect(() => { getWatch(); - }, []); + }, [id, type]); useEffect(() => { - chrome.breadcrumbs.set([ + chrome.setBreadcrumbs([ MANAGEMENT_BREADCRUMB, listBreadcrumb, id ? editBreadcrumb : createBreadcrumb, ]); - }, [id]); + }, [id, chrome, MANAGEMENT_BREADCRUMB]); const errorCode = getPageErrorCode(loadError); if (errorCode) { diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/watch_context.ts b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/watch_context.ts similarity index 100% rename from x-pack/legacy/plugins/watcher/public/sections/watch_edit/watch_context.ts rename to x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/watch_context.ts diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/watch_edit_actions.ts b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/watch_edit_actions.ts similarity index 86% rename from x-pack/legacy/plugins/watcher/public/sections/watch_edit/watch_edit_actions.ts rename to x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/watch_edit_actions.ts index 320ba59e0589..b93c2c510047 100644 --- a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/watch_edit_actions.ts +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/watch_edit_actions.ts @@ -4,11 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ +import { ToastsSetup } from 'src/core/public'; import { i18n } from '@kbn/i18n'; -import { toastNotifications } from 'ui/notify'; import { get } from 'lodash'; -import { ACTION_TYPES, WATCH_TYPES } from '../../../common/constants'; -import { BaseWatch } from '../../../common/types/watch_types'; +import { ACTION_TYPES, WATCH_TYPES } from '../../../../../common/constants'; +import { BaseWatch } from '../../../../../common/types/watch_types'; import { createWatch } from '../../lib/api'; import { goToWatchList } from '../../lib/navigation'; @@ -62,10 +62,10 @@ function createActionsForWatch(watchInstance: BaseWatch) { return watchInstance; } -export async function saveWatch(watch: BaseWatch): Promise { +export async function saveWatch(watch: BaseWatch, toasts: ToastsSetup): Promise { try { await createWatch(watch); - toastNotifications.addSuccess( + toasts.addSuccess( i18n.translate('xpack.watcher.sections.watchEdit.json.saveSuccessNotificationText', { defaultMessage: "Saved '{watchDisplayName}'", values: { @@ -75,11 +75,11 @@ export async function saveWatch(watch: BaseWatch): Promise { ); goToWatchList(); } catch (error) { - return error.response ? { error: error.response } : { error }; + return { error: error?.response.data ?? (error.body || error) }; } } -export async function onWatchSave(watch: BaseWatch): Promise { +export async function onWatchSave(watch: BaseWatch, toasts: ToastsSetup): Promise { const watchActions = watch.watch && watch.watch.actions; const watchData = watchActions ? createActionsForWatch(watch) : watch; @@ -109,7 +109,7 @@ export async function onWatchSave(watch: BaseWatch): Promise { }, }; } - return saveWatch(watchData); + return saveWatch(watchData, toasts); } - return saveWatch(watchData); + return saveWatch(watchData, toasts); } diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_list/components/watch_list.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_list/components/watch_list.tsx similarity index 97% rename from x-pack/legacy/plugins/watcher/public/sections/watch_list/components/watch_list.tsx rename to x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_list/components/watch_list.tsx index d5191c56643c..b2afc0b92509 100644 --- a/x-pack/legacy/plugins/watcher/public/sections/watch_list/components/watch_list.tsx +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_list/components/watch_list.tsx @@ -27,10 +27,8 @@ import { import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { Moment } from 'moment'; -import chrome from 'ui/chrome'; -import { MANAGEMENT_BREADCRUMB } from 'ui/management'; -import { REFRESH_INTERVALS, PAGINATION, WATCH_TYPES } from '../../../../common/constants'; +import { REFRESH_INTERVALS, PAGINATION, WATCH_TYPES } from '../../../../../../common/constants'; import { listBreadcrumb } from '../../../lib/breadcrumbs'; import { getPageErrorCode, @@ -41,12 +39,17 @@ import { SectionLoading, Error, } from '../../../components'; -import { loadWatches } from '../../../lib/api'; -import { watcherGettingStartedUrl } from '../../../lib/documentation_links'; +import { useLoadWatches } from '../../../lib/api'; import { goToCreateThresholdAlert, goToCreateAdvancedWatch } from '../../../lib/navigation'; +import { useAppContext } from '../../../app_context'; export const WatchList = () => { // hooks + const { + chrome, + legacy: { MANAGEMENT_BREADCRUMB }, + links: { watcherGettingStartedUrl }, + } = useAppContext(); const [selection, setSelection] = useState([]); const [watchesToDelete, setWatchesToDelete] = useState([]); // Filter out deleted watches on the client, because the API will return 200 even though some watches @@ -54,10 +57,10 @@ export const WatchList = () => { const [deletedWatches, setDeletedWatches] = useState([]); useEffect(() => { - chrome.breadcrumbs.set([MANAGEMENT_BREADCRUMB, listBreadcrumb]); - }, []); + chrome.setBreadcrumbs([MANAGEMENT_BREADCRUMB, listBreadcrumb]); + }, [chrome, MANAGEMENT_BREADCRUMB]); - const { isLoading: isWatchesLoading, data: watches, error } = loadWatches( + const { isLoading: isWatchesLoading, data: watches, error } = useLoadWatches( REFRESH_INTERVALS.WATCH_LIST ); diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_status/components/watch_detail.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_status/components/watch_detail.tsx similarity index 96% rename from x-pack/legacy/plugins/watcher/public/sections/watch_status/components/watch_detail.tsx rename to x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_status/components/watch_detail.tsx index aba4fd0c52a2..197342bba418 100644 --- a/x-pack/legacy/plugins/watcher/public/sections/watch_status/components/watch_detail.tsx +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_status/components/watch_detail.tsx @@ -7,7 +7,6 @@ import React, { Fragment, useState, useEffect, useContext } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { toastNotifications } from 'ui/notify'; import { EuiInMemoryTable, @@ -21,8 +20,9 @@ import { } from '@elastic/eui'; import { ackWatchAction } from '../../../lib/api'; import { WatchStatus } from '../../../components'; -import { PAGINATION } from '../../../../common/constants'; +import { PAGINATION } from '../../../../../../common/constants'; import { WatchDetailsContext } from '../watch_details_context'; +import { useAppContext } from '../../../app_context'; interface ActionError { code: string; @@ -36,6 +36,7 @@ interface ActionStatus { } export const WatchDetail = () => { + const { toasts } = useAppContext(); const { watchDetail } = useContext(WatchDetailsContext); const [actionStatuses, setActionStatuses] = useState([]); @@ -60,7 +61,7 @@ export const WatchDetail = () => { }; }); setActionStatuses(actionStatusesWithErrors); - }, [watchDetail]); + }, [watchDetail, actionErrors, currentActionStatuses]); const baseColumns = [ { @@ -144,7 +145,7 @@ export const WatchDetail = () => { return setActionStatuses(newActionStatusesWithErrors); } catch (e) { setIsActionStatusLoading(false); - toastNotifications.addDanger( + toasts.addDanger( i18n.translate( 'xpack.watcher.sections.watchDetail.watchTable.ackActionErrorMessage', { diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_status/components/watch_history.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_status/components/watch_history.tsx similarity index 97% rename from x-pack/legacy/plugins/watcher/public/sections/watch_status/components/watch_history.tsx rename to x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_status/components/watch_history.tsx index bf6ca0c6c43a..2bc1a0cbace1 100644 --- a/x-pack/legacy/plugins/watcher/public/sections/watch_status/components/watch_history.tsx +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_status/components/watch_history.tsx @@ -23,9 +23,9 @@ import { EuiTitle, } from '@elastic/eui'; -import { PAGINATION } from '../../../../common/constants'; +import { PAGINATION } from '../../../../../../common/constants'; import { WatchStatus, SectionError, Error } from '../../../components'; -import { loadWatchHistory, loadWatchHistoryDetail } from '../../../lib/api'; +import { useLoadWatchHistory, useLoadWatchHistoryDetail } from '../../../lib/api'; import { WatchDetailsContext } from '../watch_details_context'; const watchHistoryTimeSpanOptions = [ @@ -83,12 +83,12 @@ export const WatchHistory = () => { setIsActivated(isActive); } - const { error: historyError, data: history, isLoading } = loadWatchHistory( + const { error: historyError, data: history, isLoading } = useLoadWatchHistory( loadedWatch.id, watchHistoryTimeSpan ); - const { error: watchHistoryDetailsError, data: watchHistoryDetails } = loadWatchHistoryDetail( + const { error: watchHistoryDetailsError, data: watchHistoryDetails } = useLoadWatchHistoryDetail( detailWatchId ); diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_status/components/watch_status.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_status/components/watch_status.tsx similarity index 94% rename from x-pack/legacy/plugins/watcher/public/sections/watch_status/components/watch_status.tsx rename to x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_status/components/watch_status.tsx index 413e8f638887..53817c23e72e 100644 --- a/x-pack/legacy/plugins/watcher/public/sections/watch_status/components/watch_status.tsx +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_status/components/watch_status.tsx @@ -17,15 +17,12 @@ import { EuiBadge, EuiButtonEmpty, } from '@elastic/eui'; -import chrome from 'ui/chrome'; -import { MANAGEMENT_BREADCRUMB } from 'ui/management'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { toastNotifications } from 'ui/notify'; import { WatchDetail } from './watch_detail'; import { WatchHistory } from './watch_history'; import { listBreadcrumb, statusBreadcrumb } from '../../../lib/breadcrumbs'; -import { loadWatchDetail, deactivateWatch, activateWatch } from '../../../lib/api'; +import { useLoadWatchDetail, deactivateWatch, activateWatch } from '../../../lib/api'; import { WatchDetailsContext } from '../watch_details_context'; import { getPageErrorCode, @@ -34,6 +31,7 @@ import { DeleteWatchesModal, } from '../../../components'; import { goToWatchList } from '../../../lib/navigation'; +import { useAppContext } from '../../../app_context'; interface WatchStatusTab { id: string; @@ -69,11 +67,16 @@ export const WatchStatus = ({ }; }; }) => { + const { + chrome, + legacy: { MANAGEMENT_BREADCRUMB }, + toasts, + } = useAppContext(); const { error: watchDetailError, data: watchDetail, isLoading: isWatchDetailLoading, - } = loadWatchDetail(id); + } = useLoadWatchDetail(id); const [selectedTab, setSelectedTab] = useState(WATCH_EXECUTION_HISTORY_TAB); const [isActivated, setIsActivated] = useState(undefined); @@ -81,8 +84,8 @@ export const WatchStatus = ({ const [isTogglingActivation, setIsTogglingActivation] = useState(false); useEffect(() => { - chrome.breadcrumbs.set([MANAGEMENT_BREADCRUMB, listBreadcrumb, statusBreadcrumb]); - }, [id]); + chrome.setBreadcrumbs([MANAGEMENT_BREADCRUMB, listBreadcrumb, statusBreadcrumb]); + }, [id, chrome, MANAGEMENT_BREADCRUMB]); const errorCode = getPageErrorCode(watchDetailError); @@ -148,7 +151,7 @@ export const WatchStatus = ({ defaultMessage: "Couldn't activate watch", } ); - return toastNotifications.addDanger(message); + return toasts.addDanger(message); } setIsActivated(!isActivated); diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_status/watch_details_context.ts b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_status/watch_details_context.ts similarity index 100% rename from x-pack/legacy/plugins/watcher/public/sections/watch_status/watch_details_context.ts rename to x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_status/watch_details_context.ts diff --git a/x-pack/legacy/plugins/watcher/public/shared_imports.ts b/x-pack/legacy/plugins/watcher/public/np_ready/application/shared_imports.ts similarity index 79% rename from x-pack/legacy/plugins/watcher/public/shared_imports.ts rename to x-pack/legacy/plugins/watcher/public/np_ready/application/shared_imports.ts index 3d93b882733a..60445b00c098 100644 --- a/x-pack/legacy/plugins/watcher/public/shared_imports.ts +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/shared_imports.ts @@ -10,4 +10,4 @@ export { UseRequestConfig, sendRequest, useRequest, -} from '../../../../../src/plugins/es_ui_shared/public/request'; +} from '../../../../../../../src/plugins/es_ui_shared/public/request/np_ready_request'; diff --git a/x-pack/legacy/plugins/watcher/public/index.js b/x-pack/legacy/plugins/watcher/public/np_ready/index.ts similarity index 71% rename from x-pack/legacy/plugins/watcher/public/index.js rename to x-pack/legacy/plugins/watcher/public/np_ready/index.ts index c1b84e76d000..ff635579316e 100644 --- a/x-pack/legacy/plugins/watcher/public/index.js +++ b/x-pack/legacy/plugins/watcher/public/np_ready/index.ts @@ -3,6 +3,6 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import { WatcherUIPlugin } from './plugin'; -import './register_route'; -import './register_management_sections'; +export const plugin = () => new WatcherUIPlugin(); diff --git a/x-pack/legacy/plugins/watcher/public/np_ready/plugin.ts b/x-pack/legacy/plugins/watcher/public/np_ready/plugin.ts new file mode 100644 index 000000000000..161de9b5fc06 --- /dev/null +++ b/x-pack/legacy/plugins/watcher/public/np_ready/plugin.ts @@ -0,0 +1,62 @@ +/* + * 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 { Plugin, CoreSetup, CoreStart } from 'src/core/public'; + +import { LegacyDependencies } from './types'; + +interface LegacyPlugins { + __LEGACY: LegacyDependencies; +} + +export class WatcherUIPlugin implements Plugin { + /* TODO: Remove this in future. We need this at mount (setup) but it's only available on start plugins. */ + euiUtils: any = null; + + setup({ application, notifications, http, uiSettings }: CoreSetup, { __LEGACY }: LegacyPlugins) { + application.register({ + id: 'watcher', + title: 'Watcher', + mount: async ( + { + core: { + docLinks, + chrome, + // Waiting for types to be updated. + // @ts-ignore + savedObjects, + i18n: { Context: I18nContext }, + }, + }, + { element } + ) => { + const euiUtils = this.euiUtils!; + const { boot } = await import('./application/boot'); + return boot({ + element, + toasts: notifications.toasts, + http, + uiSettings, + docLinks, + chrome, + euiUtils, + savedObjects: savedObjects.client, + I18nContext, + legacy: { + ...__LEGACY, + }, + }); + }, + }); + } + + start(core: CoreStart, { eui_utils }: any) { + // eslint-disable-next-line @typescript-eslint/camelcase + this.euiUtils = eui_utils; + } + + stop() {} +} diff --git a/x-pack/legacy/plugins/watcher/server/routes/api/fields/register_fields_routes.js b/x-pack/legacy/plugins/watcher/public/np_ready/types.ts similarity index 63% rename from x-pack/legacy/plugins/watcher/server/routes/api/fields/register_fields_routes.js rename to x-pack/legacy/plugins/watcher/public/np_ready/types.ts index 64b9a14f9c43..22109f99c2c4 100644 --- a/x-pack/legacy/plugins/watcher/server/routes/api/fields/register_fields_routes.js +++ b/x-pack/legacy/plugins/watcher/public/np_ready/types.ts @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { registerListRoute } from './register_list_route'; - -export function registerFieldsRoutes(server) { - registerListRoute(server); +export interface LegacyDependencies { + MANAGEMENT_BREADCRUMB: { text: string; href?: string }; + TimeBuckets: any; + licenseStatus: any; } diff --git a/x-pack/legacy/plugins/watcher/public/register_feature.js b/x-pack/legacy/plugins/watcher/public/register_feature.js deleted file mode 100644 index 5dd4f28f03bc..000000000000 --- a/x-pack/legacy/plugins/watcher/public/register_feature.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 { FeatureCatalogueRegistryProvider, FeatureCatalogueCategory } from 'ui/registry/feature_catalogue'; -import { i18n } from '@kbn/i18n'; - -FeatureCatalogueRegistryProvider.register(() => { - return { - id: 'watcher', - title: 'Watcher', // This is a product name so we don't translate it. - description: i18n.translate('xpack.watcher.watcherDescription', { - defaultMessage: 'Detect changes in your data by creating, managing, and monitoring alerts.' - }), - icon: 'watchesApp', - path: '/app/kibana#/management/elasticsearch/watcher/watches', - showOnHomePage: true, - category: FeatureCatalogueCategory.ADMIN - }; -}); diff --git a/x-pack/legacy/plugins/watcher/public/register_feature.ts b/x-pack/legacy/plugins/watcher/public/register_feature.ts new file mode 100644 index 000000000000..0de41e09f788 --- /dev/null +++ b/x-pack/legacy/plugins/watcher/public/register_feature.ts @@ -0,0 +1,21 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { npSetup } from 'ui/new_platform'; +import { FeatureCatalogueCategory } from 'ui/registry/feature_catalogue'; + +npSetup.plugins.home.featureCatalogue.register({ + id: 'watcher', + title: 'Watcher', // This is a product name so we don't translate it. + category: FeatureCatalogueCategory.ADMIN, + description: i18n.translate('xpack.watcher.watcherDescription', { + defaultMessage: 'Detect changes in your data by creating, managing, and monitoring alerts.', + }), + icon: 'watchesApp', + path: '/app/kibana#/management/elasticsearch/watcher/watches', + showOnHomePage: true, +}); diff --git a/x-pack/legacy/plugins/watcher/public/register_management_sections.js b/x-pack/legacy/plugins/watcher/public/register_management_sections.js deleted file mode 100644 index 886ac7d28db6..000000000000 --- a/x-pack/legacy/plugins/watcher/public/register_management_sections.js +++ /dev/null @@ -1,60 +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 { management } from 'ui/management'; -import { i18n } from '@kbn/i18n'; - -management.getSection('elasticsearch').register('watcher', { - display: i18n.translate('xpack.watcher.sections.watchList.managementSection.watcherDisplayName', { - defaultMessage: 'Watcher', - }), - order: 6, - url: '#/management/elasticsearch/watcher/', -}); - -management.getSection('elasticsearch/watcher').register('watches', { - display: i18n.translate('xpack.watcher.sections.watchList.managementSection.watchesDisplayName', { - defaultMessage: 'Watches', - }), - order: 1, -}); - -management.getSection('elasticsearch/watcher').register('watch', { - visible: false, -}); - -management.getSection('elasticsearch/watcher/watch').register('status', { - display: i18n.translate('xpack.watcher.sections.watchList.managementSection.statusDisplayName', { - defaultMessage: 'Status', - }), - order: 1, - visible: false, -}); - -management.getSection('elasticsearch/watcher/watch').register('edit', { - display: i18n.translate('xpack.watcher.sections.watchList.managementSection.editDisplayName', { - defaultMessage: 'Edit', - }), - order: 2, - visible: false, -}); - -management.getSection('elasticsearch/watcher/watch').register('new', { - display: i18n.translate( - 'xpack.watcher.sections.watchList.managementSection.newWatchDisplayName', - { - defaultMessage: 'New Watch', - } - ), - order: 1, - visible: false, -}); - -management.getSection('elasticsearch/watcher/watch').register('history-item', { - order: 1, - visible: false, -}); diff --git a/x-pack/legacy/plugins/watcher/public/register_route.js b/x-pack/legacy/plugins/watcher/public/register_route.js deleted file mode 100644 index c58be17bc6e7..000000000000 --- a/x-pack/legacy/plugins/watcher/public/register_route.js +++ /dev/null @@ -1,68 +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 { render, unmountComponentAtNode } from 'react-dom'; -import { SavedObjectsClientProvider } from 'ui/saved_objects'; -import routes from 'ui/routes'; -import { management } from 'ui/management'; -import { xpackInfo } from 'plugins/xpack_main/services/xpack_info'; -import template from './app.html'; -import { App } from './app'; -import { setHttpClient, setSavedObjectsClient } from './lib/api'; -import { I18nContext } from 'ui/i18n'; -import { manageAngularLifecycle } from './lib/manage_angular_lifecycle'; -import { PLUGIN } from '../common/constants'; -import { LICENSE_STATUS_UNAVAILABLE, LICENSE_STATUS_INVALID } from '../../../common/constants'; - -let elem; -const renderReact = async (elem, licenseStatus) => { - render( - - - , - elem - ); -}; -routes.when('/management/elasticsearch/watcher/:param1?/:param2?/:param3?/:param4?', { - template, - controller: class WatcherController { - constructor($injector, $scope, $http, Private) { - const $route = $injector.get('$route'); - const licenseStatus = xpackInfo.get(`features.${PLUGIN.ID}`); - - // clean up previously rendered React app if one exists - // this happens because of React Router redirects - elem && unmountComponentAtNode(elem); - setSavedObjectsClient(Private(SavedObjectsClientProvider)); - // NOTE: We depend upon Angular's $http service because it's decorated with interceptors, - // e.g. to check license status per request. - setHttpClient($http); - $scope.$$postDigest(() => { - elem = document.getElementById('watchReactRoot'); - renderReact(elem, licenseStatus); - manageAngularLifecycle($scope, $route, elem); - }); - } - }, - controllerAs: 'watchRoute', -}); - -routes.defaults(/\/management/, { - resolve: { - watcherManagementSection: () => { - const watchesSection = management.getSection('elasticsearch/watcher'); - const licenseStatus = xpackInfo.get(`features.${PLUGIN.ID}`); - const { status } = licenseStatus; - - if (status === LICENSE_STATUS_INVALID || status === LICENSE_STATUS_UNAVAILABLE) { - return watchesSection.hide(); - } - - watchesSection.show(); - - }, - }, -}); diff --git a/x-pack/legacy/plugins/watcher/server/lib/call_with_internal_user_factory/call_with_internal_user_factory.js b/x-pack/legacy/plugins/watcher/server/lib/call_with_internal_user_factory/call_with_internal_user_factory.js deleted file mode 100644 index b0ca09060106..000000000000 --- a/x-pack/legacy/plugins/watcher/server/lib/call_with_internal_user_factory/call_with_internal_user_factory.js +++ /dev/null @@ -1,18 +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 { once } from 'lodash'; - -const _callWithInternalUser = once((server) => { - const { callWithInternalUser } = server.plugins.elasticsearch.getCluster('admin'); - return callWithInternalUser; -}); - -export const callWithInternalUserFactory = (server) => { - return (...args) => { - return _callWithInternalUser(server)(...args); - }; -}; diff --git a/x-pack/legacy/plugins/watcher/server/lib/call_with_internal_user_factory/index.js b/x-pack/legacy/plugins/watcher/server/lib/call_with_internal_user_factory/index.js deleted file mode 100644 index a56a50e2864a..000000000000 --- a/x-pack/legacy/plugins/watcher/server/lib/call_with_internal_user_factory/index.js +++ /dev/null @@ -1,7 +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. - */ - -export { callWithInternalUserFactory } from './call_with_internal_user_factory'; diff --git a/x-pack/legacy/plugins/watcher/server/lib/call_with_request_factory/call_with_request_factory.js b/x-pack/legacy/plugins/watcher/server/lib/call_with_request_factory/call_with_request_factory.js deleted file mode 100644 index f60f825b9800..000000000000 --- a/x-pack/legacy/plugins/watcher/server/lib/call_with_request_factory/call_with_request_factory.js +++ /dev/null @@ -1,21 +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 { once } from 'lodash'; -import { elasticsearchJsPlugin } from '../elasticsearch_js_plugin'; - -const callWithRequest = once((server) => { - const config = { plugins: [ elasticsearchJsPlugin ] }; - const cluster = server.plugins.elasticsearch.createCluster('watcher', config); - - return cluster.callWithRequest; -}); - -export const callWithRequestFactory = (server, request) => { - return (...args) => { - return callWithRequest(server)(request, ...args); - }; -}; diff --git a/x-pack/legacy/plugins/watcher/server/lib/call_with_request_factory/index.js b/x-pack/legacy/plugins/watcher/server/lib/call_with_request_factory/index.js deleted file mode 100644 index 787814d87dff..000000000000 --- a/x-pack/legacy/plugins/watcher/server/lib/call_with_request_factory/index.js +++ /dev/null @@ -1,7 +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. - */ - -export { callWithRequestFactory } from './call_with_request_factory'; diff --git a/x-pack/legacy/plugins/watcher/server/lib/elasticsearch_js_plugin/index.js b/x-pack/legacy/plugins/watcher/server/lib/elasticsearch_js_plugin/index.js deleted file mode 100644 index 87b5ff5426c9..000000000000 --- a/x-pack/legacy/plugins/watcher/server/lib/elasticsearch_js_plugin/index.js +++ /dev/null @@ -1,7 +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. - */ - -export { elasticsearchJsPlugin } from './elasticsearch_js_plugin'; diff --git a/x-pack/legacy/plugins/watcher/server/lib/error_wrappers/__tests__/wrap_custom_error.js b/x-pack/legacy/plugins/watcher/server/lib/error_wrappers/__tests__/wrap_custom_error.js deleted file mode 100644 index f9c102be7a1f..000000000000 --- a/x-pack/legacy/plugins/watcher/server/lib/error_wrappers/__tests__/wrap_custom_error.js +++ /dev/null @@ -1,21 +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 expect from '@kbn/expect'; -import { wrapCustomError } from '../wrap_custom_error'; - -describe('wrap_custom_error', () => { - describe('#wrapCustomError', () => { - it('should return a Boom object', () => { - const originalError = new Error('I am an error'); - const statusCode = 404; - const wrappedError = wrapCustomError(originalError, statusCode); - - expect(wrappedError.isBoom).to.be(true); - expect(wrappedError.output.statusCode).to.equal(statusCode); - }); - }); -}); diff --git a/x-pack/legacy/plugins/watcher/server/lib/error_wrappers/__tests__/wrap_es_error.js b/x-pack/legacy/plugins/watcher/server/lib/error_wrappers/__tests__/wrap_es_error.js deleted file mode 100644 index 467cc4fcdae1..000000000000 --- a/x-pack/legacy/plugins/watcher/server/lib/error_wrappers/__tests__/wrap_es_error.js +++ /dev/null @@ -1,39 +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 expect from '@kbn/expect'; -import { wrapEsError } from '../wrap_es_error'; - -describe('wrap_es_error', () => { - describe('#wrapEsError', () => { - - let originalError; - beforeEach(() => { - originalError = new Error('I am an error'); - originalError.statusCode = 404; - }); - - it('should return a Boom object', () => { - const wrappedError = wrapEsError(originalError); - - expect(wrappedError.isBoom).to.be(true); - }); - - it('should return the correct Boom object', () => { - const wrappedError = wrapEsError(originalError); - - expect(wrappedError.output.statusCode).to.be(originalError.statusCode); - expect(wrappedError.output.payload.message).to.be(originalError.message); - }); - - it('should return the correct Boom object with custom message', () => { - const wrappedError = wrapEsError(originalError, { 404: 'No encontrado!' }); - - expect(wrappedError.output.statusCode).to.be(originalError.statusCode); - expect(wrappedError.output.payload.message).to.be('No encontrado!'); - }); - }); -}); diff --git a/x-pack/legacy/plugins/watcher/server/lib/error_wrappers/__tests__/wrap_unknown_error.js b/x-pack/legacy/plugins/watcher/server/lib/error_wrappers/__tests__/wrap_unknown_error.js deleted file mode 100644 index 85e0b2b3033a..000000000000 --- a/x-pack/legacy/plugins/watcher/server/lib/error_wrappers/__tests__/wrap_unknown_error.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 expect from '@kbn/expect'; -import { wrapUnknownError } from '../wrap_unknown_error'; - -describe('wrap_unknown_error', () => { - describe('#wrapUnknownError', () => { - it('should return a Boom object', () => { - const originalError = new Error('I am an error'); - const wrappedError = wrapUnknownError(originalError); - - expect(wrappedError.isBoom).to.be(true); - }); - }); -}); diff --git a/x-pack/legacy/plugins/watcher/server/lib/error_wrappers/index.js b/x-pack/legacy/plugins/watcher/server/lib/error_wrappers/index.js deleted file mode 100644 index f275f1563709..000000000000 --- a/x-pack/legacy/plugins/watcher/server/lib/error_wrappers/index.js +++ /dev/null @@ -1,9 +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. - */ - -export { wrapCustomError } from './wrap_custom_error'; -export { wrapEsError } from './wrap_es_error'; -export { wrapUnknownError } from './wrap_unknown_error'; diff --git a/x-pack/legacy/plugins/watcher/server/lib/error_wrappers/wrap_custom_error.js b/x-pack/legacy/plugins/watcher/server/lib/error_wrappers/wrap_custom_error.js deleted file mode 100644 index 3295113d38ee..000000000000 --- a/x-pack/legacy/plugins/watcher/server/lib/error_wrappers/wrap_custom_error.js +++ /dev/null @@ -1,18 +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 Boom from 'boom'; - -/** - * Wraps a custom error into a Boom error response and returns it - * - * @param err Object error - * @param statusCode Error status code - * @return Object Boom error response - */ -export function wrapCustomError(err, statusCode) { - return Boom.boomify(err, { statusCode }); -} diff --git a/x-pack/legacy/plugins/watcher/server/lib/error_wrappers/wrap_es_error.js b/x-pack/legacy/plugins/watcher/server/lib/error_wrappers/wrap_es_error.js deleted file mode 100644 index 2df2e4b802e1..000000000000 --- a/x-pack/legacy/plugins/watcher/server/lib/error_wrappers/wrap_es_error.js +++ /dev/null @@ -1,30 +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 Boom from 'boom'; - -/** - * Wraps an error thrown by the ES JS client into a Boom error response and returns it - * - * @param err Object Error thrown by ES JS client - * @param statusCodeToMessageMap Object Optional map of HTTP status codes => error messages - * @return Object Boom error response - */ -export function wrapEsError(err, statusCodeToMessageMap = {}) { - - const statusCode = err.statusCode; - - // If no custom message if specified for the error's status code, just - // wrap the error as a Boom error response and return it - if (!statusCodeToMessageMap[statusCode]) { - return Boom.boomify(err, { statusCode }); - } - - // Otherwise, use the custom message to create a Boom error response and - // return it - const message = statusCodeToMessageMap[statusCode]; - return new Boom(message, { statusCode }); -} diff --git a/x-pack/legacy/plugins/watcher/server/lib/error_wrappers/wrap_unknown_error.js b/x-pack/legacy/plugins/watcher/server/lib/error_wrappers/wrap_unknown_error.js deleted file mode 100644 index ffd915c51336..000000000000 --- a/x-pack/legacy/plugins/watcher/server/lib/error_wrappers/wrap_unknown_error.js +++ /dev/null @@ -1,17 +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 Boom from 'boom'; - -/** - * Wraps an unknown error into a Boom error response and returns it - * - * @param err Object Unknown error - * @return Object Boom error response - */ -export function wrapUnknownError(err) { - return Boom.boomify(err); -} diff --git a/x-pack/legacy/plugins/watcher/server/lib/is_es_error_factory/__tests__/is_es_error_factory.js b/x-pack/legacy/plugins/watcher/server/lib/is_es_error_factory/__tests__/is_es_error_factory.js deleted file mode 100644 index 76fdf7b36c3d..000000000000 --- a/x-pack/legacy/plugins/watcher/server/lib/is_es_error_factory/__tests__/is_es_error_factory.js +++ /dev/null @@ -1,48 +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 expect from '@kbn/expect'; -import { isEsErrorFactory } from '../is_es_error_factory'; -import { set } from 'lodash'; - -class MockAbstractEsError {} - -describe('is_es_error_factory', () => { - - let mockServer; - let isEsError; - - beforeEach(() => { - const mockEsErrors = { - _Abstract: MockAbstractEsError - }; - mockServer = {}; - set(mockServer, 'plugins.elasticsearch.getCluster', () => ({ errors: mockEsErrors })); - - isEsError = isEsErrorFactory(mockServer); - }); - - describe('#isEsErrorFactory', () => { - - it('should return a function', () => { - expect(isEsError).to.be.a(Function); - }); - - describe('returned function', () => { - - it('should return true if passed-in err is a known esError', () => { - const knownEsError = new MockAbstractEsError(); - expect(isEsError(knownEsError)).to.be(true); - }); - - it('should return false if passed-in err is not a known esError', () => { - const unknownEsError = {}; - expect(isEsError(unknownEsError)).to.be(false); - - }); - }); - }); -}); diff --git a/x-pack/legacy/plugins/watcher/server/lib/is_es_error_factory/is_es_error_factory.js b/x-pack/legacy/plugins/watcher/server/lib/is_es_error_factory/is_es_error_factory.js deleted file mode 100644 index 80daac5bd496..000000000000 --- a/x-pack/legacy/plugins/watcher/server/lib/is_es_error_factory/is_es_error_factory.js +++ /dev/null @@ -1,18 +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 { memoize } from 'lodash'; - -const esErrorsFactory = memoize((server) => { - return server.plugins.elasticsearch.getCluster('admin').errors; -}); - -export function isEsErrorFactory(server) { - const esErrors = esErrorsFactory(server); - return function isEsError(err) { - return err instanceof esErrors._Abstract; - }; -} diff --git a/x-pack/legacy/plugins/watcher/server/lib/license_pre_routing_factory/license_pre_routing_factory.js b/x-pack/legacy/plugins/watcher/server/lib/license_pre_routing_factory/license_pre_routing_factory.js deleted file mode 100644 index 5b34108c9c1c..000000000000 --- a/x-pack/legacy/plugins/watcher/server/lib/license_pre_routing_factory/license_pre_routing_factory.js +++ /dev/null @@ -1,31 +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 { once } from 'lodash'; -import { wrapCustomError } from '../error_wrappers'; -import { PLUGIN } from '../../../common/constants'; -import { LICENSE_STATUS_VALID } from '../../../../../common/constants/license_status'; - -export const licensePreRoutingFactory = once((server) => { - const xpackMainPlugin = server.plugins.xpack_main; - - // License checking and enable/disable logic - function licensePreRouting() { - const licenseCheckResults = xpackMainPlugin.info.feature(PLUGIN.ID).getLicenseCheckResults(); - const { status } = licenseCheckResults; - - if (status !== LICENSE_STATUS_VALID) { - const error = new Error(licenseCheckResults.message); - const statusCode = 403; - throw wrapCustomError(error, statusCode); - } - - return null; - } - - return licensePreRouting; -}); - diff --git a/x-pack/legacy/plugins/watcher/server/np_ready/index.ts b/x-pack/legacy/plugins/watcher/server/np_ready/index.ts new file mode 100644 index 000000000000..3f5e1a91209e --- /dev/null +++ b/x-pack/legacy/plugins/watcher/server/np_ready/index.ts @@ -0,0 +1,9 @@ +/* + * 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 { PluginInitializerContext } from 'src/core/server'; +import { WatcherServerPlugin } from './plugin'; + +export const plugin = (ctx: PluginInitializerContext) => new WatcherServerPlugin(); diff --git a/x-pack/legacy/plugins/watcher/server/np_ready/lib/call_with_request_factory.ts b/x-pack/legacy/plugins/watcher/server/np_ready/lib/call_with_request_factory.ts new file mode 100644 index 000000000000..eaec9cd91b23 --- /dev/null +++ b/x-pack/legacy/plugins/watcher/server/np_ready/lib/call_with_request_factory.ts @@ -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 { ElasticsearchServiceSetup } from 'src/core/server'; +import { once } from 'lodash'; +import { elasticsearchJsPlugin } from './elasticsearch_js_plugin'; + +const callWithRequest = once((elasticsearchService: ElasticsearchServiceSetup) => { + const config = { plugins: [elasticsearchJsPlugin] }; + return elasticsearchService.createClient('watcher', config); +}); + +export const callWithRequestFactory = ( + elasticsearchService: ElasticsearchServiceSetup, + request: any +) => { + return (...args: any[]) => { + return ( + callWithRequest(elasticsearchService) + .asScoped(request) + // @ts-ignore + .callAsCurrentUser(...args) + ); + }; +}; diff --git a/x-pack/legacy/plugins/watcher/server/lib/elasticsearch_js_plugin/elasticsearch_js_plugin.js b/x-pack/legacy/plugins/watcher/server/np_ready/lib/elasticsearch_js_plugin.ts similarity index 84% rename from x-pack/legacy/plugins/watcher/server/lib/elasticsearch_js_plugin/elasticsearch_js_plugin.js rename to x-pack/legacy/plugins/watcher/server/np_ready/lib/elasticsearch_js_plugin.ts index ad42388beea1..240e93e160fe 100644 --- a/x-pack/legacy/plugins/watcher/server/lib/elasticsearch_js_plugin/elasticsearch_js_plugin.js +++ b/x-pack/legacy/plugins/watcher/server/np_ready/lib/elasticsearch_js_plugin.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -export const elasticsearchJsPlugin = (Client, config, components) => { +export const elasticsearchJsPlugin = (Client: any, config: any, components: any) => { const ca = components.clientAction.factory; Client.prototype.watcher = components.clientAction.namespaceFactory(); @@ -21,19 +21,19 @@ export const elasticsearchJsPlugin = (Client, config, components) => { params: { masterTimeout: { name: 'master_timeout', - type: 'duration' - } + type: 'duration', + }, }, url: { fmt: '/_watcher/watch/<%=id%>/_deactivate', req: { id: { type: 'string', - required: true - } - } + required: true, + }, + }, }, - method: 'PUT' + method: 'PUT', }); /** @@ -47,19 +47,19 @@ export const elasticsearchJsPlugin = (Client, config, components) => { params: { masterTimeout: { name: 'master_timeout', - type: 'duration' - } + type: 'duration', + }, }, url: { fmt: '/_watcher/watch/<%=id%>/_activate', req: { id: { type: 'string', - required: true - } - } + required: true, + }, + }, }, - method: 'PUT' + method: 'PUT', }); /** @@ -74,23 +74,23 @@ export const elasticsearchJsPlugin = (Client, config, components) => { params: { masterTimeout: { name: 'master_timeout', - type: 'duration' - } + type: 'duration', + }, }, url: { fmt: '/_watcher/watch/<%=id%>/_ack/<%=action%>', req: { id: { type: 'string', - required: true + required: true, }, action: { type: 'string', - required: true - } - } + required: true, + }, + }, }, - method: 'POST' + method: 'POST', }); /** @@ -105,22 +105,22 @@ export const elasticsearchJsPlugin = (Client, config, components) => { params: { masterTimeout: { name: 'master_timeout', - type: 'duration' + type: 'duration', }, force: { - type: 'boolean' - } + type: 'boolean', + }, }, url: { fmt: '/_watcher/watch/<%=id%>', req: { id: { type: 'string', - required: true - } - } + required: true, + }, + }, }, - method: 'DELETE' + method: 'DELETE', }); /** @@ -132,14 +132,14 @@ export const elasticsearchJsPlugin = (Client, config, components) => { params: { masterTimeout: { name: 'master_timeout', - type: 'duration' - } + type: 'duration', + }, }, url: { - fmt: '/_watcher/watch/_execute' + fmt: '/_watcher/watch/_execute', }, needBody: true, - method: 'POST' + method: 'POST', }); /** @@ -155,10 +155,10 @@ export const elasticsearchJsPlugin = (Client, config, components) => { req: { id: { type: 'string', - required: true - } - } - } + required: true, + }, + }, + }, }); /** @@ -172,20 +172,20 @@ export const elasticsearchJsPlugin = (Client, config, components) => { params: { masterTimeout: { name: 'master_timeout', - type: 'duration' - } + type: 'duration', + }, }, url: { fmt: '/_watcher/watch/<%=id%>', req: { id: { type: 'string', - required: true - } - } + required: true, + }, + }, }, needBody: true, - method: 'PUT' + method: 'PUT', }); /** @@ -196,9 +196,9 @@ export const elasticsearchJsPlugin = (Client, config, components) => { watcher.restart = ca({ params: {}, url: { - fmt: '/_watcher/_restart' + fmt: '/_watcher/_restart', }, - method: 'PUT' + method: 'PUT', }); /** @@ -209,9 +209,9 @@ export const elasticsearchJsPlugin = (Client, config, components) => { watcher.start = ca({ params: {}, url: { - fmt: '/_watcher/_start' + fmt: '/_watcher/_start', }, - method: 'PUT' + method: 'PUT', }); /** @@ -222,8 +222,8 @@ export const elasticsearchJsPlugin = (Client, config, components) => { watcher.stats = ca({ params: {}, url: { - fmt: '/_watcher/stats' - } + fmt: '/_watcher/stats', + }, }); /** @@ -234,8 +234,8 @@ export const elasticsearchJsPlugin = (Client, config, components) => { watcher.stop = ca({ params: {}, url: { - fmt: '/_watcher/_stop' + fmt: '/_watcher/_stop', }, - method: 'PUT' + method: 'PUT', }); }; diff --git a/x-pack/legacy/plugins/watcher/server/lib/fetch_all_from_scroll/__tests__/fetch_all_from_scroll.js b/x-pack/legacy/plugins/watcher/server/np_ready/lib/fetch_all_from_scroll/__tests__/fetch_all_from_scroll.js similarity index 100% rename from x-pack/legacy/plugins/watcher/server/lib/fetch_all_from_scroll/__tests__/fetch_all_from_scroll.js rename to x-pack/legacy/plugins/watcher/server/np_ready/lib/fetch_all_from_scroll/__tests__/fetch_all_from_scroll.js diff --git a/x-pack/legacy/plugins/watcher/server/lib/fetch_all_from_scroll/fetch_all_from_scroll.js b/x-pack/legacy/plugins/watcher/server/np_ready/lib/fetch_all_from_scroll/fetch_all_from_scroll.ts similarity index 64% rename from x-pack/legacy/plugins/watcher/server/lib/fetch_all_from_scroll/fetch_all_from_scroll.js rename to x-pack/legacy/plugins/watcher/server/np_ready/lib/fetch_all_from_scroll/fetch_all_from_scroll.ts index eb76d5d3731c..d762b05f01d7 100644 --- a/x-pack/legacy/plugins/watcher/server/lib/fetch_all_from_scroll/fetch_all_from_scroll.js +++ b/x-pack/legacy/plugins/watcher/server/np_ready/lib/fetch_all_from_scroll/fetch_all_from_scroll.ts @@ -5,9 +5,9 @@ */ import { get } from 'lodash'; -import { ES_SCROLL_SETTINGS } from '../../../common/constants'; +import { ES_SCROLL_SETTINGS } from '../../../../common/constants'; -export function fetchAllFromScroll(response, callWithRequest, hits = []) { +export function fetchAllFromScroll(response: any, callWithRequest: any, hits: any[] = []) { const newHits = get(response, 'hits.hits', []); const scrollId = get(response, '_scroll_id'); @@ -17,12 +17,11 @@ export function fetchAllFromScroll(response, callWithRequest, hits = []) { return callWithRequest('scroll', { body: { scroll: ES_SCROLL_SETTINGS.KEEPALIVE, - scroll_id: scrollId - } - }) - .then(innerResponse => { - return fetchAllFromScroll(innerResponse, callWithRequest, hits); - }); + scroll_id: scrollId, + }, + }).then((innerResponse: any) => { + return fetchAllFromScroll(innerResponse, callWithRequest, hits); + }); } return Promise.resolve(hits); diff --git a/x-pack/legacy/plugins/watcher/server/lib/fetch_all_from_scroll/index.js b/x-pack/legacy/plugins/watcher/server/np_ready/lib/fetch_all_from_scroll/index.ts similarity index 100% rename from x-pack/legacy/plugins/watcher/server/lib/fetch_all_from_scroll/index.js rename to x-pack/legacy/plugins/watcher/server/np_ready/lib/fetch_all_from_scroll/index.ts diff --git a/x-pack/legacy/plugins/watcher/server/lib/is_es_error_factory/index.js b/x-pack/legacy/plugins/watcher/server/np_ready/lib/is_es_error/index.ts similarity index 80% rename from x-pack/legacy/plugins/watcher/server/lib/is_es_error_factory/index.js rename to x-pack/legacy/plugins/watcher/server/np_ready/lib/is_es_error/index.ts index 441648a8701e..a9a3c61472d8 100644 --- a/x-pack/legacy/plugins/watcher/server/lib/is_es_error_factory/index.js +++ b/x-pack/legacy/plugins/watcher/server/np_ready/lib/is_es_error/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { isEsErrorFactory } from './is_es_error_factory'; +export { isEsError } from './is_es_error'; diff --git a/x-pack/legacy/plugins/watcher/server/np_ready/lib/is_es_error/is_es_error.ts b/x-pack/legacy/plugins/watcher/server/np_ready/lib/is_es_error/is_es_error.ts new file mode 100644 index 000000000000..4137293cf39c --- /dev/null +++ b/x-pack/legacy/plugins/watcher/server/np_ready/lib/is_es_error/is_es_error.ts @@ -0,0 +1,13 @@ +/* + * 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 legacyElasticsearch from 'elasticsearch'; + +const esErrorsParent = legacyElasticsearch.errors._Abstract; + +export function isEsError(err: Error) { + return err instanceof esErrorsParent; +} diff --git a/x-pack/legacy/plugins/watcher/server/lib/license_pre_routing_factory/__tests__/license_pre_routing_factory.js b/x-pack/legacy/plugins/watcher/server/np_ready/lib/license_pre_routing_factory/__tests__/license_pre_routing_factory.js similarity index 71% rename from x-pack/legacy/plugins/watcher/server/lib/license_pre_routing_factory/__tests__/license_pre_routing_factory.js rename to x-pack/legacy/plugins/watcher/server/np_ready/lib/license_pre_routing_factory/__tests__/license_pre_routing_factory.js index ed4a51a11b7c..fc01e42e6fdf 100644 --- a/x-pack/legacy/plugins/watcher/server/lib/license_pre_routing_factory/__tests__/license_pre_routing_factory.js +++ b/x-pack/legacy/plugins/watcher/server/np_ready/lib/license_pre_routing_factory/__tests__/license_pre_routing_factory.js @@ -5,8 +5,9 @@ */ import expect from '@kbn/expect'; +import { kibanaResponseFactory } from '../../../../../../../../../src/core/server'; import { licensePreRoutingFactory } from '../license_pre_routing_factory'; -import { LICENSE_STATUS_VALID, LICENSE_STATUS_EXPIRED } from '../../../../../../common/constants/license_status'; +import { LICENSE_STATUS_VALID, LICENSE_STATUS_EXPIRED } from '../../../../../../../common/constants/license_status'; describe('license_pre_routing_factory', () => { describe('#reportingFeaturePreRoutingFactory', () => { @@ -27,13 +28,6 @@ describe('license_pre_routing_factory', () => { }; }); - it('only instantiates one instance per server', () => { - const firstInstance = licensePreRoutingFactory(mockServer); - const secondInstance = licensePreRoutingFactory(mockServer); - - expect(firstInstance).to.be(secondInstance); - }); - describe('status is not valid', () => { beforeEach(() => { mockLicenseCheckResults = { @@ -42,13 +36,10 @@ describe('license_pre_routing_factory', () => { }); it ('replies with 403', () => { - const licensePreRouting = licensePreRoutingFactory(mockServer); + const licensePreRouting = licensePreRoutingFactory(mockServer, () => {}); const stubRequest = {}; - expect(() => licensePreRouting(stubRequest)).to.throwException((response) => { - expect(response).to.be.an(Error); - expect(response.isBoom).to.be(true); - expect(response.output.statusCode).to.be(403); - }); + const response = licensePreRouting({}, stubRequest, kibanaResponseFactory); + expect(response.status).to.be(403); }); }); @@ -60,9 +51,9 @@ describe('license_pre_routing_factory', () => { }); it ('replies with nothing', () => { - const licensePreRouting = licensePreRoutingFactory(mockServer); + const licensePreRouting = licensePreRoutingFactory(mockServer, () => null); const stubRequest = {}; - const response = licensePreRouting(stubRequest); + const response = licensePreRouting({}, stubRequest, kibanaResponseFactory); expect(response).to.be(null); }); }); diff --git a/x-pack/legacy/plugins/watcher/server/lib/license_pre_routing_factory/index.js b/x-pack/legacy/plugins/watcher/server/np_ready/lib/license_pre_routing_factory/index.ts similarity index 100% rename from x-pack/legacy/plugins/watcher/server/lib/license_pre_routing_factory/index.js rename to x-pack/legacy/plugins/watcher/server/np_ready/lib/license_pre_routing_factory/index.ts diff --git a/x-pack/legacy/plugins/watcher/server/np_ready/lib/license_pre_routing_factory/license_pre_routing_factory.ts b/x-pack/legacy/plugins/watcher/server/np_ready/lib/license_pre_routing_factory/license_pre_routing_factory.ts new file mode 100644 index 000000000000..d2f496724610 --- /dev/null +++ b/x-pack/legacy/plugins/watcher/server/np_ready/lib/license_pre_routing_factory/license_pre_routing_factory.ts @@ -0,0 +1,43 @@ +/* + * 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 { + KibanaRequest, + KibanaResponseFactory, + RequestHandler, + RequestHandlerContext, +} from 'src/core/server'; +import { PLUGIN } from '../../../../common/constants'; +import { LICENSE_STATUS_VALID } from '../../../../../../common/constants/license_status'; +import { ServerShim } from '../../types'; + +export const licensePreRoutingFactory = ( + server: ServerShim, + handler: RequestHandler +): RequestHandler => { + const xpackMainPlugin = server.plugins.xpack_main; + + // License checking and enable/disable logic + return function licensePreRouting( + ctx: RequestHandlerContext, + request: KibanaRequest, + response: KibanaResponseFactory + ) { + const licenseCheckResults = xpackMainPlugin.info.feature(PLUGIN.ID).getLicenseCheckResults(); + const { status } = licenseCheckResults; + + if (status !== LICENSE_STATUS_VALID) { + return response.customError({ + body: { + message: licenseCheckResults.messsage, + }, + statusCode: 403, + }); + } + + return handler(ctx, request, response); + }; +}; diff --git a/x-pack/legacy/plugins/watcher/server/lib/normalized_field_types/index.js b/x-pack/legacy/plugins/watcher/server/np_ready/lib/normalized_field_types/index.ts similarity index 100% rename from x-pack/legacy/plugins/watcher/server/lib/normalized_field_types/index.js rename to x-pack/legacy/plugins/watcher/server/np_ready/lib/normalized_field_types/index.ts diff --git a/x-pack/legacy/plugins/watcher/server/lib/normalized_field_types/normalized_field_types.js b/x-pack/legacy/plugins/watcher/server/np_ready/lib/normalized_field_types/normalized_field_types.ts similarity index 61% rename from x-pack/legacy/plugins/watcher/server/lib/normalized_field_types/normalized_field_types.js rename to x-pack/legacy/plugins/watcher/server/np_ready/lib/normalized_field_types/normalized_field_types.ts index 65f2867662bd..39e82e7db896 100644 --- a/x-pack/legacy/plugins/watcher/server/lib/normalized_field_types/normalized_field_types.js +++ b/x-pack/legacy/plugins/watcher/server/np_ready/lib/normalized_field_types/normalized_field_types.ts @@ -5,12 +5,12 @@ */ export const normalizedFieldTypes = { - 'long': 'number', - 'integer': 'number', - 'short': 'number', - 'byte': 'number', - 'double': 'number', - 'float': 'number', - 'half_float': 'number', - 'scaled_float': 'number' + long: 'number', + integer: 'number', + short: 'number', + byte: 'number', + double: 'number', + float: 'number', + half_float: 'number', + scaled_float: 'number', }; diff --git a/x-pack/legacy/plugins/watcher/server/models/action_status/__tests__/action_status.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/action_status/__tests__/action_status.js similarity index 99% rename from x-pack/legacy/plugins/watcher/server/models/action_status/__tests__/action_status.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/action_status/__tests__/action_status.js index 456768c8c02e..430669ab26c5 100644 --- a/x-pack/legacy/plugins/watcher/server/models/action_status/__tests__/action_status.js +++ b/x-pack/legacy/plugins/watcher/server/np_ready/models/action_status/__tests__/action_status.js @@ -6,7 +6,7 @@ import expect from '@kbn/expect'; import { ActionStatus } from '../action_status'; -import { ACTION_STATES } from '../../../../common/constants'; +import { ACTION_STATES } from '../../../../../common/constants'; import moment from 'moment'; describe('action_status', () => { diff --git a/x-pack/legacy/plugins/watcher/server/models/action_status/action_status.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/action_status/action_status.js similarity index 97% rename from x-pack/legacy/plugins/watcher/server/models/action_status/action_status.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/action_status/action_status.js index eeedf9aefe5f..7f724cf68211 100644 --- a/x-pack/legacy/plugins/watcher/server/models/action_status/action_status.js +++ b/x-pack/legacy/plugins/watcher/server/np_ready/models/action_status/action_status.js @@ -6,8 +6,8 @@ import { get } from 'lodash'; import { badImplementation, badRequest } from 'boom'; -import { getMoment } from '../../../common/lib/get_moment'; -import { ACTION_STATES } from '../../../common/constants'; +import { getMoment } from '../../../../common/lib/get_moment'; +import { ACTION_STATES } from '../../../../common/constants'; import { i18n } from '@kbn/i18n'; export class ActionStatus { diff --git a/x-pack/legacy/plugins/watcher/server/models/action_status/index.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/action_status/index.js similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/action_status/index.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/action_status/index.js diff --git a/x-pack/legacy/plugins/watcher/server/models/execute_details/__tests__/execute_details.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/execute_details/__tests__/execute_details.js similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/execute_details/__tests__/execute_details.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/execute_details/__tests__/execute_details.js diff --git a/x-pack/legacy/plugins/watcher/server/models/execute_details/execute_details.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/execute_details/execute_details.js similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/execute_details/execute_details.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/execute_details/execute_details.js diff --git a/x-pack/legacy/plugins/watcher/server/models/execute_details/index.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/execute_details/index.js similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/execute_details/index.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/execute_details/index.js diff --git a/x-pack/legacy/plugins/watcher/server/models/fields/__tests__/fields.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/fields/__tests__/fields.js similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/fields/__tests__/fields.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/fields/__tests__/fields.js diff --git a/x-pack/legacy/plugins/watcher/server/models/fields/fields.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/fields/fields.js similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/fields/fields.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/fields/fields.js diff --git a/x-pack/legacy/plugins/watcher/server/models/fields/index.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/fields/index.js similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/fields/index.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/fields/index.js diff --git a/x-pack/legacy/plugins/watcher/server/models/settings/__tests__/settings.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/settings/__tests__/settings.js similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/settings/__tests__/settings.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/settings/__tests__/settings.js diff --git a/x-pack/legacy/plugins/watcher/server/models/settings/index.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/settings/index.js similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/settings/index.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/settings/index.js diff --git a/x-pack/legacy/plugins/watcher/server/models/settings/settings.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/settings/settings.js similarity index 97% rename from x-pack/legacy/plugins/watcher/server/models/settings/settings.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/settings/settings.js index 95a1db7533f4..55622117efed 100644 --- a/x-pack/legacy/plugins/watcher/server/models/settings/settings.js +++ b/x-pack/legacy/plugins/watcher/server/np_ready/models/settings/settings.js @@ -5,7 +5,7 @@ */ import { merge } from 'lodash'; -import { ACTION_TYPES } from '../../../common/constants'; +import { ACTION_TYPES } from '../../../../common/constants'; function isEnabledByDefault(actionType) { switch (actionType) { diff --git a/x-pack/legacy/plugins/watcher/server/models/visualize_options/index.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/visualize_options/index.js similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/visualize_options/index.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/visualize_options/index.js diff --git a/x-pack/legacy/plugins/watcher/server/models/visualize_options/visualize_options.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/visualize_options/visualize_options.js similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/visualize_options/visualize_options.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/visualize_options/visualize_options.js diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/base_watch.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/base_watch.js similarity index 98% rename from x-pack/legacy/plugins/watcher/server/models/watch/base_watch.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch/base_watch.js index f96274594872..6a6df7d6f7f7 100644 --- a/x-pack/legacy/plugins/watcher/server/models/watch/base_watch.js +++ b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/base_watch.js @@ -6,7 +6,7 @@ import { get, map, pick } from 'lodash'; import { badRequest } from 'boom'; -import { Action } from '../../../common/models/action'; +import { Action } from '../../../../common/models/action'; import { WatchStatus } from '../watch_status'; import { i18n } from '@kbn/i18n'; import { WatchErrors } from '../watch_errors'; diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/base_watch.test.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/base_watch.test.js similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/watch/base_watch.test.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch/base_watch.test.js diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/index.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/index.js similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/watch/index.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch/index.js diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/json_watch.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/json_watch.js similarity index 93% rename from x-pack/legacy/plugins/watcher/server/models/watch/json_watch.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch/json_watch.js index e319cc1bc277..0b011ca33a76 100644 --- a/x-pack/legacy/plugins/watcher/server/models/watch/json_watch.js +++ b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/json_watch.js @@ -6,8 +6,8 @@ import { isEmpty, cloneDeep, has, merge } from 'lodash'; import { BaseWatch } from './base_watch'; -import { WATCH_TYPES } from '../../../common/constants'; -import { serializeJsonWatch } from '../../../common/lib/serialization'; +import { WATCH_TYPES } from '../../../../common/constants'; +import { serializeJsonWatch } from '../../../../common/lib/serialization'; export class JsonWatch extends BaseWatch { // This constructor should not be used directly. diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/json_watch.test.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/json_watch.test.js similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/watch/json_watch.test.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch/json_watch.test.js diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/lib/get_watch_type/get_watch_type.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/lib/get_watch_type/get_watch_type.js similarity index 88% rename from x-pack/legacy/plugins/watcher/server/models/watch/lib/get_watch_type/get_watch_type.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch/lib/get_watch_type/get_watch_type.js index 2bdd03e23c6d..72c725eda2bd 100644 --- a/x-pack/legacy/plugins/watcher/server/models/watch/lib/get_watch_type/get_watch_type.js +++ b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/lib/get_watch_type/get_watch_type.js @@ -5,7 +5,7 @@ */ import { get, contains, values } from 'lodash'; -import { WATCH_TYPES } from '../../../../../common/constants'; +import { WATCH_TYPES } from '../../../../../../common/constants'; export function getWatchType(watchJson) { const type = get(watchJson, 'metadata.xpack.type'); diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/lib/get_watch_type/index.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/lib/get_watch_type/index.js similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/watch/lib/get_watch_type/index.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch/lib/get_watch_type/index.js diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/monitoring_watch.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/monitoring_watch.js similarity index 97% rename from x-pack/legacy/plugins/watcher/server/models/watch/monitoring_watch.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch/monitoring_watch.js index 977c62726a03..7f29d41b20fb 100644 --- a/x-pack/legacy/plugins/watcher/server/models/watch/monitoring_watch.js +++ b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/monitoring_watch.js @@ -7,7 +7,7 @@ import { merge } from 'lodash'; import { badRequest } from 'boom'; import { BaseWatch } from './base_watch'; -import { WATCH_TYPES } from '../../../common/constants'; +import { WATCH_TYPES } from '../../../../common/constants'; import { i18n } from '@kbn/i18n'; export class MonitoringWatch extends BaseWatch { diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/monitoring_watch.test.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/monitoring_watch.test.js similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/watch/monitoring_watch.test.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch/monitoring_watch.test.js diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/__tests__/format_visualize_data.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/__tests__/format_visualize_data.js similarity index 99% rename from x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/__tests__/format_visualize_data.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/__tests__/format_visualize_data.js index 04239ab6e1b5..a7524bcc7c4d 100644 --- a/x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/__tests__/format_visualize_data.js +++ b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/__tests__/format_visualize_data.js @@ -5,7 +5,7 @@ */ import expect from '@kbn/expect'; -import { AGG_TYPES } from '../../../../../common/constants'; +import { AGG_TYPES } from '../../../../../../common/constants'; import { formatVisualizeData } from '../format_visualize_data'; describe('watch', () => { diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/build_visualize_query.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/build_visualize_query.js similarity index 95% rename from x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/build_visualize_query.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/build_visualize_query.js index ab9daf6f636a..c3b73d23d96b 100644 --- a/x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/build_visualize_query.js +++ b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/build_visualize_query.js @@ -5,8 +5,8 @@ */ import { cloneDeep } from 'lodash'; -import { buildInput } from '../../../../common/lib/serialization'; -import { AGG_TYPES } from '../../../../common/constants'; +import { buildInput } from '../../../../../common/lib/serialization'; +import { AGG_TYPES } from '../../../../../common/constants'; /* input.search.request.body.query.bool.filter.range diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/data_samples/count.json b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/data_samples/count.json similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/data_samples/count.json rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/data_samples/count.json diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/data_samples/count.query.date.json b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/data_samples/count.query.date.json similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/data_samples/count.query.date.json rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/data_samples/count.query.date.json diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/data_samples/count.query.json b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/data_samples/count.query.json similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/data_samples/count.query.json rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/data_samples/count.query.json diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/data_samples/count_terms.json b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/data_samples/count_terms.json similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/data_samples/count_terms.json rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/data_samples/count_terms.json diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/data_samples/count_terms.query.date.json b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/data_samples/count_terms.query.date.json similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/data_samples/count_terms.query.date.json rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/data_samples/count_terms.query.date.json diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/data_samples/count_terms.query.json b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/data_samples/count_terms.query.json similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/data_samples/count_terms.query.json rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/data_samples/count_terms.query.json diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/data_samples/non_count.json b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/data_samples/non_count.json similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/data_samples/non_count.json rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/data_samples/non_count.json diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/data_samples/non_count.query.date.json b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/data_samples/non_count.query.date.json similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/data_samples/non_count.query.date.json rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/data_samples/non_count.query.date.json diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/data_samples/non_count.query.json b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/data_samples/non_count.query.json similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/data_samples/non_count.query.json rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/data_samples/non_count.query.json diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/data_samples/non_count_terms.json b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/data_samples/non_count_terms.json similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/data_samples/non_count_terms.json rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/data_samples/non_count_terms.json diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/data_samples/non_count_terms.query.date.json b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/data_samples/non_count_terms.query.date.json similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/data_samples/non_count_terms.query.date.json rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/data_samples/non_count_terms.query.date.json diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/data_samples/non_count_terms.query.json b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/data_samples/non_count_terms.query.json similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/data_samples/non_count_terms.query.json rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/data_samples/non_count_terms.query.json diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/format_visualize_data.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/format_visualize_data.js similarity index 97% rename from x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/format_visualize_data.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/format_visualize_data.js index 90cdc9464e8c..19d41d2491cf 100644 --- a/x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/format_visualize_data.js +++ b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/format_visualize_data.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AGG_TYPES } from '../../../../common/constants'; +import { AGG_TYPES } from '../../../../../common/constants'; export function formatVisualizeData({ aggType, termField }, results) { if (aggType === AGG_TYPES.COUNT && !Boolean(termField)) { diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/index.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/index.js similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/index.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/index.js diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/threshold_watch.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/threshold_watch.js similarity index 97% rename from x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/threshold_watch.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/threshold_watch.js index cb40c46ac643..db662902d0f4 100644 --- a/x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/threshold_watch.js +++ b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/threshold_watch.js @@ -6,8 +6,8 @@ import { merge } from 'lodash'; import { BaseWatch } from '../base_watch'; -import { WATCH_TYPES, COMPARATORS, SORT_ORDERS } from '../../../../common/constants'; -import { serializeThresholdWatch } from '../../../../common/lib/serialization'; +import { WATCH_TYPES, COMPARATORS, SORT_ORDERS } from '../../../../../common/constants'; +import { serializeThresholdWatch } from '../../../../../common/lib/serialization'; import { buildVisualizeQuery } from './build_visualize_query'; import { formatVisualizeData } from './format_visualize_data'; diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/threshold_watch.test.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/threshold_watch.test.js similarity index 99% rename from x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/threshold_watch.test.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/threshold_watch.test.js index 4a0b7b657bbc..6226a702d7f3 100644 --- a/x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/threshold_watch.test.js +++ b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/threshold_watch.test.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { COMPARATORS, SORT_ORDERS } from '../../../../common/constants'; +import { COMPARATORS, SORT_ORDERS } from '../../../../../common/constants'; import { WatchErrors } from '../../watch_errors'; import { ThresholdWatch } from './threshold_watch'; diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/watch.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/watch.js similarity index 97% rename from x-pack/legacy/plugins/watcher/server/models/watch/watch.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch/watch.js index c75afc62c4c4..10b021dcbedf 100644 --- a/x-pack/legacy/plugins/watcher/server/models/watch/watch.js +++ b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/watch.js @@ -6,7 +6,7 @@ import { set } from 'lodash'; import { badRequest } from 'boom'; -import { WATCH_TYPES } from '../../../common/constants'; +import { WATCH_TYPES } from '../../../../common/constants'; import { JsonWatch } from './json_watch'; import { MonitoringWatch } from './monitoring_watch'; import { ThresholdWatch } from './threshold_watch'; diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/watch.test.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/watch.test.js similarity index 98% rename from x-pack/legacy/plugins/watcher/server/models/watch/watch.test.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch/watch.test.js index 2895c23083de..c419c2856173 100644 --- a/x-pack/legacy/plugins/watcher/server/models/watch/watch.test.js +++ b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/watch.test.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { WATCH_TYPES } from '../../../common/constants'; +import { WATCH_TYPES } from '../../../../common/constants'; import { Watch } from './watch'; import { JsonWatch } from './json_watch'; import { MonitoringWatch } from './monitoring_watch'; diff --git a/x-pack/legacy/plugins/watcher/server/models/watch_errors/index.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch_errors/index.js similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/watch_errors/index.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch_errors/index.js diff --git a/x-pack/legacy/plugins/watcher/server/models/watch_errors/watch_errors.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch_errors/watch_errors.js similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/watch_errors/watch_errors.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch_errors/watch_errors.js diff --git a/x-pack/legacy/plugins/watcher/server/models/watch_errors/watch_errors.test.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch_errors/watch_errors.test.js similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/watch_errors/watch_errors.test.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch_errors/watch_errors.test.js diff --git a/x-pack/legacy/plugins/watcher/server/models/watch_history_item/__tests__/watch_history_item.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch_history_item/__tests__/watch_history_item.js similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/watch_history_item/__tests__/watch_history_item.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch_history_item/__tests__/watch_history_item.js diff --git a/x-pack/legacy/plugins/watcher/server/models/watch_history_item/index.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch_history_item/index.js similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/watch_history_item/index.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch_history_item/index.js diff --git a/x-pack/legacy/plugins/watcher/server/models/watch_history_item/watch_history_item.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch_history_item/watch_history_item.js similarity index 97% rename from x-pack/legacy/plugins/watcher/server/models/watch_history_item/watch_history_item.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch_history_item/watch_history_item.js index 617f75857174..5172e590fc63 100644 --- a/x-pack/legacy/plugins/watcher/server/models/watch_history_item/watch_history_item.js +++ b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch_history_item/watch_history_item.js @@ -5,7 +5,7 @@ */ import { badRequest } from 'boom'; -import { getMoment } from '../../../common/lib/get_moment'; +import { getMoment } from '../../../../common/lib/get_moment'; import { get, cloneDeep } from 'lodash'; import { WatchStatus } from '../watch_status'; import { i18n } from '@kbn/i18n'; diff --git a/x-pack/legacy/plugins/watcher/server/models/watch_status/__tests__/watch_status.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch_status/__tests__/watch_status.js similarity index 99% rename from x-pack/legacy/plugins/watcher/server/models/watch_status/__tests__/watch_status.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch_status/__tests__/watch_status.js index e29c8dd2a529..9a045fa4b5a7 100644 --- a/x-pack/legacy/plugins/watcher/server/models/watch_status/__tests__/watch_status.js +++ b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch_status/__tests__/watch_status.js @@ -6,7 +6,7 @@ import expect from '@kbn/expect'; import { WatchStatus } from '../watch_status'; -import { ACTION_STATES, WATCH_STATES, WATCH_STATE_COMMENTS } from '../../../../common/constants'; +import { ACTION_STATES, WATCH_STATES, WATCH_STATE_COMMENTS } from '../../../../../common/constants'; import moment from 'moment'; describe('watch_status', () => { diff --git a/x-pack/legacy/plugins/watcher/server/models/watch_status/index.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch_status/index.js similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/watch_status/index.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch_status/index.js diff --git a/x-pack/legacy/plugins/watcher/server/models/watch_status/watch_status.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch_status/watch_status.js similarity index 98% rename from x-pack/legacy/plugins/watcher/server/models/watch_status/watch_status.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch_status/watch_status.js index b7cffe16ca0b..1e3d1d3064cb 100644 --- a/x-pack/legacy/plugins/watcher/server/models/watch_status/watch_status.js +++ b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch_status/watch_status.js @@ -6,9 +6,9 @@ import { get, map, forEach, max } from 'lodash'; import { badRequest } from 'boom'; -import { getMoment } from '../../../common/lib/get_moment'; +import { getMoment } from '../../../../common/lib/get_moment'; import { ActionStatus } from '../action_status'; -import { ACTION_STATES, WATCH_STATES, WATCH_STATE_COMMENTS } from '../../../common/constants'; +import { ACTION_STATES, WATCH_STATES, WATCH_STATE_COMMENTS } from '../../../../common/constants'; import { i18n } from '@kbn/i18n'; function getActionStatusTotals(watchStatus) { diff --git a/x-pack/legacy/plugins/watcher/server/np_ready/plugin.ts b/x-pack/legacy/plugins/watcher/server/np_ready/plugin.ts new file mode 100644 index 000000000000..2e8c81efa19c --- /dev/null +++ b/x-pack/legacy/plugins/watcher/server/np_ready/plugin.ts @@ -0,0 +1,51 @@ +/* + * 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 { first } from 'rxjs/operators'; +import { Plugin, CoreSetup } from 'src/core/server'; +import { i18n } from '@kbn/i18n'; +import { PLUGIN } from '../../common/constants'; +import { ServerShim, RouteDependencies } from './types'; + +import { registerLicenseChecker } from '../../../../server/lib/register_license_checker'; +import { registerSettingsRoutes } from './routes/api/settings'; +import { registerIndicesRoutes } from './routes/api/indices'; +import { registerLicenseRoutes } from './routes/api/license'; +import { registerWatchesRoutes } from './routes/api/watches'; +import { registerWatchRoutes } from './routes/api/watch'; +import { registerListFieldsRoute } from './routes/api/register_list_fields_route'; +import { registerLoadHistoryRoute } from './routes/api/register_load_history_route'; + +export class WatcherServerPlugin implements Plugin { + async setup( + { http, elasticsearch: elasticsearchService }: CoreSetup, + { __LEGACY: serverShim }: { __LEGACY: ServerShim } + ) { + const elasticsearch = await elasticsearchService.adminClient$.pipe(first()).toPromise(); + const router = http.createRouter(); + const routeDependencies: RouteDependencies = { + elasticsearch, + elasticsearchService, + router, + }; + // Register license checker + registerLicenseChecker( + serverShim as any, + PLUGIN.ID, + PLUGIN.getI18nName(i18n), + PLUGIN.MINIMUM_LICENSE_REQUIRED + ); + + registerListFieldsRoute(routeDependencies, serverShim); + registerLoadHistoryRoute(routeDependencies, serverShim); + registerIndicesRoutes(routeDependencies, serverShim); + registerLicenseRoutes(routeDependencies, serverShim); + registerSettingsRoutes(routeDependencies, serverShim); + registerWatchesRoutes(routeDependencies, serverShim); + registerWatchRoutes(routeDependencies, serverShim); + } + start() {} + stop() {} +} diff --git a/x-pack/legacy/plugins/watcher/server/routes/api/indices/index.js b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/indices/index.ts similarity index 100% rename from x-pack/legacy/plugins/watcher/server/routes/api/indices/index.js rename to x-pack/legacy/plugins/watcher/server/np_ready/routes/api/indices/index.ts diff --git a/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/indices/register_get_route.ts b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/indices/register_get_route.ts new file mode 100644 index 000000000000..6b6b643dc4ad --- /dev/null +++ b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/indices/register_get_route.ts @@ -0,0 +1,92 @@ +/* + * 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 { schema } from '@kbn/config-schema'; +import { RequestHandler } from 'src/core/server'; +import { reduce, size } from 'lodash'; +import { callWithRequestFactory } from '../../../lib/call_with_request_factory'; +import { isEsError } from '../../../lib/is_es_error'; +import { licensePreRoutingFactory } from '../../../lib/license_pre_routing_factory'; +import { RouteDependencies, ServerShim } from '../../../types'; + +function getIndexNamesFromAliasesResponse(json: Record) { + return reduce( + json, + (list, { aliases }, indexName) => { + list.push(indexName); + if (size(aliases) > 0) { + list.push(...Object.keys(aliases)); + } + return list; + }, + [] as string[] + ); +} + +function getIndices(callWithRequest: any, pattern: string, limit = 10) { + return callWithRequest('indices.getAlias', { + index: pattern, + ignore: [404], + }).then((aliasResult: any) => { + if (aliasResult.status !== 404) { + const indicesFromAliasResponse = getIndexNamesFromAliasesResponse(aliasResult); + return indicesFromAliasResponse.slice(0, limit); + } + + const params = { + index: pattern, + ignore: [404], + body: { + size: 0, // no hits + aggs: { + indices: { + terms: { + field: '_index', + size: limit, + }, + }, + }, + }, + }; + + return callWithRequest('search', params).then((response: any) => { + if (response.status === 404 || !response.aggregations) { + return []; + } + return response.aggregations.indices.buckets.map((bucket: any) => bucket.key); + }); + }); +} + +export function registerGetRoute(deps: RouteDependencies, legacy: ServerShim) { + const handler: RequestHandler = async (ctx, request, response) => { + const callWithRequest = callWithRequestFactory(deps.elasticsearchService, request); + const { pattern } = request.body; + + try { + const indices = await getIndices(callWithRequest, pattern); + return response.ok({ body: { indices } }); + } catch (e) { + // Case: Error from Elasticsearch JS client + if (isEsError(e)) { + return response.customError({ statusCode: e.statusCode, body: e }); + } + + // Case: default + return response.internalError({ body: e }); + } + }; + + deps.router.post( + { + path: '/api/watcher/indices', + validate: { + body: schema.object({}, { allowUnknowns: true }), + }, + }, + licensePreRoutingFactory(legacy, handler) + ); +} diff --git a/x-pack/legacy/plugins/watcher/server/routes/api/indices/register_indices_routes.js b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/indices/register_indices_routes.ts similarity index 62% rename from x-pack/legacy/plugins/watcher/server/routes/api/indices/register_indices_routes.js rename to x-pack/legacy/plugins/watcher/server/np_ready/routes/api/indices/register_indices_routes.ts index 41b2f8dba7a1..647a85c31153 100644 --- a/x-pack/legacy/plugins/watcher/server/routes/api/indices/register_indices_routes.js +++ b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/indices/register_indices_routes.ts @@ -5,7 +5,8 @@ */ import { registerGetRoute } from './register_get_route'; +import { RouteDependencies, ServerShim } from '../../../types'; -export function registerIndicesRoutes(server) { - registerGetRoute(server); +export function registerIndicesRoutes(deps: RouteDependencies, legacy: ServerShim) { + registerGetRoute(deps, legacy); } diff --git a/x-pack/legacy/plugins/watcher/server/routes/api/license/index.js b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/license/index.ts similarity index 100% rename from x-pack/legacy/plugins/watcher/server/routes/api/license/index.js rename to x-pack/legacy/plugins/watcher/server/np_ready/routes/api/license/index.ts diff --git a/x-pack/legacy/plugins/watcher/server/routes/api/license/register_license_routes.js b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/license/register_license_routes.ts similarity index 62% rename from x-pack/legacy/plugins/watcher/server/routes/api/license/register_license_routes.js rename to x-pack/legacy/plugins/watcher/server/np_ready/routes/api/license/register_license_routes.ts index fe890719a0a7..c5965d9315b0 100644 --- a/x-pack/legacy/plugins/watcher/server/routes/api/license/register_license_routes.js +++ b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/license/register_license_routes.ts @@ -5,7 +5,8 @@ */ import { registerRefreshRoute } from './register_refresh_route'; +import { RouteDependencies, ServerShim } from '../../../types'; -export function registerLicenseRoutes(server) { - registerRefreshRoute(server); +export function registerLicenseRoutes(deps: RouteDependencies, legacy: ServerShim) { + registerRefreshRoute(deps, legacy); } diff --git a/x-pack/legacy/plugins/watcher/server/routes/api/license/register_refresh_route.js b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/license/register_refresh_route.ts similarity index 50% rename from x-pack/legacy/plugins/watcher/server/routes/api/license/register_refresh_route.js rename to x-pack/legacy/plugins/watcher/server/np_ready/routes/api/license/register_refresh_route.ts index cbd5dc7f6631..08f1f26a84a4 100644 --- a/x-pack/legacy/plugins/watcher/server/routes/api/license/register_refresh_route.js +++ b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/license/register_refresh_route.ts @@ -4,7 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { licensePreRoutingFactory } from'../../../lib/license_pre_routing_factory'; +import { RequestHandler } from 'src/core/server'; +import { licensePreRoutingFactory } from '../../../lib/license_pre_routing_factory'; +import { RouteDependencies, ServerShim } from '../../../types'; /* In order for the client to have the most up-to-date snapshot of the current license, @@ -12,17 +14,16 @@ it needs to make a round-trip to the kibana server. This refresh endpoint is pro for when the client needs to check the license, but doesn't need to pull data from the server for any reason, i.e., when adding a new watch. */ -export function registerRefreshRoute(server) { - const licensePreRouting = licensePreRoutingFactory(server); +export function registerRefreshRoute(deps: RouteDependencies, legacy: ServerShim) { + const handler: RequestHandler = (ctx, request, response) => { + return response.ok({ body: { success: true } }); + }; - server.route({ - path: '/api/watcher/license/refresh', - method: 'GET', - handler: () => { - return { success: true }; + deps.router.get( + { + path: '/api/watcher/license/refresh', + validate: false, }, - config: { - pre: [ licensePreRouting ] - } - }); + licensePreRoutingFactory(legacy, handler) + ); } diff --git a/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/register_list_fields_route.ts b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/register_list_fields_route.ts new file mode 100644 index 000000000000..f3222d24f0ad --- /dev/null +++ b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/register_list_fields_route.ts @@ -0,0 +1,65 @@ +/* + * 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 { schema } from '@kbn/config-schema'; +import { RequestHandler } from 'src/core/server'; +import { callWithRequestFactory } from '../../lib/call_with_request_factory'; +import { isEsError } from '../../lib/is_es_error'; +import { licensePreRoutingFactory } from '../../lib/license_pre_routing_factory'; +// @ts-ignore +import { Fields } from '../../models/fields'; +import { RouteDependencies, ServerShim } from '../../types'; + +function fetchFields(callWithRequest: any, indexes: string[]) { + const params = { + index: indexes, + fields: ['*'], + ignoreUnavailable: true, + allowNoIndices: true, + ignore: 404, + }; + + return callWithRequest('fieldCaps', params); +} + +export function registerListFieldsRoute(deps: RouteDependencies, legacy: ServerShim) { + const handler: RequestHandler = async (ctx, request, response) => { + const callWithRequest = callWithRequestFactory(deps.elasticsearchService, request); + const { indexes } = request.body; + + try { + const fieldsResponse = await fetchFields(callWithRequest, indexes); + const json = fieldsResponse.status === 404 ? { fields: [] } : fieldsResponse; + const fields = Fields.fromUpstreamJson(json); + return response.ok({ body: fields.downstreamJson }); + } catch (e) { + // Case: Error from Elasticsearch JS client + if (isEsError(e)) { + return response.customError({ + statusCode: e.statusCode, + body: { + message: e.message, + }, + }); + } + + // Case: default + return response.internalError({ body: e }); + } + }; + + deps.router.post( + { + path: '/api/watcher/fields', + validate: { + body: schema.object({ + indexes: schema.arrayOf(schema.string()), + }), + }, + }, + licensePreRoutingFactory(legacy, handler) + ); +} diff --git a/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/register_load_history_route.ts b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/register_load_history_route.ts new file mode 100644 index 000000000000..d62e4f48c562 --- /dev/null +++ b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/register_load_history_route.ts @@ -0,0 +1,77 @@ +/* + * 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 { schema } from '@kbn/config-schema'; +import { get } from 'lodash'; +import { RequestHandler } from 'src/core/server'; +import { callWithRequestFactory } from '../../lib/call_with_request_factory'; +import { isEsError } from '../../lib/is_es_error'; +import { INDEX_NAMES } from '../../../../common/constants'; +import { licensePreRoutingFactory } from '../../lib/license_pre_routing_factory'; +import { RouteDependencies, ServerShim } from '../../types'; +// @ts-ignore +import { WatchHistoryItem } from '../../models/watch_history_item'; + +function fetchHistoryItem(callWithRequest: any, watchHistoryItemId: string) { + return callWithRequest('search', { + index: INDEX_NAMES.WATCHER_HISTORY, + body: { + query: { + bool: { + must: [{ term: { _id: watchHistoryItemId } }], + }, + }, + }, + }); +} + +export function registerLoadHistoryRoute(deps: RouteDependencies, legacy: ServerShim) { + const handler: RequestHandler = async (ctx, request, response) => { + const callWithRequest = callWithRequestFactory(deps.elasticsearchService, request); + const id = request.params.id; + + try { + const responseFromES = await fetchHistoryItem(callWithRequest, id); + const hit = get(responseFromES, 'hits.hits[0]'); + if (!hit) { + return response.notFound({ body: `Watch History Item with id = ${id} not found` }); + } + const watchHistoryItemJson = get(hit, '_source'); + const watchId = get(hit, '_source.watch_id'); + const json = { + id, + watchId, + watchHistoryItemJson, + includeDetails: true, + }; + + const watchHistoryItem = WatchHistoryItem.fromUpstreamJson(json); + return response.ok({ + body: { watchHistoryItem: watchHistoryItem.downstreamJson }, + }); + } catch (e) { + // Case: Error from Elasticsearch JS client + if (isEsError(e)) { + return response.customError({ statusCode: e.statusCode, body: e }); + } + + // Case: default + return response.internalError({ body: e }); + } + }; + + deps.router.get( + { + path: '/api/watcher/history/{id}', + validate: { + params: schema.object({ + id: schema.string(), + }), + }, + }, + licensePreRoutingFactory(legacy, handler) + ); +} diff --git a/x-pack/legacy/plugins/watcher/server/routes/api/settings/index.js b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/settings/index.ts similarity index 100% rename from x-pack/legacy/plugins/watcher/server/routes/api/settings/index.js rename to x-pack/legacy/plugins/watcher/server/np_ready/routes/api/settings/index.ts diff --git a/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/settings/register_load_route.ts b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/settings/register_load_route.ts new file mode 100644 index 000000000000..710d079d810d --- /dev/null +++ b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/settings/register_load_route.ts @@ -0,0 +1,43 @@ +/* + * 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 { IClusterClient, RequestHandler } from 'src/core/server'; +import { isEsError } from '../../../lib/is_es_error'; +import { licensePreRoutingFactory } from '../../../lib/license_pre_routing_factory'; +// @ts-ignore +import { Settings } from '../../../models/settings'; +import { RouteDependencies, ServerShim } from '../../../types'; + +function fetchClusterSettings(client: IClusterClient) { + return client.callAsInternalUser('cluster.getSettings', { + includeDefaults: true, + filterPath: '**.xpack.notification', + }); +} + +export function registerLoadRoute(deps: RouteDependencies, legacy: ServerShim) { + const handler: RequestHandler = async (ctx, request, response) => { + try { + const settings = await fetchClusterSettings(deps.elasticsearch); + return response.ok({ body: Settings.fromUpstreamJson(settings).downstreamJson }); + } catch (e) { + // Case: Error from Elasticsearch JS client + if (isEsError(e)) { + return response.customError({ statusCode: e.statusCode, body: e }); + } + + // Case: default + return response.internalError({ body: e }); + } + }; + deps.router.get( + { + path: '/api/watcher/settings', + validate: false, + }, + licensePreRoutingFactory(legacy, handler) + ); +} diff --git a/x-pack/legacy/plugins/watcher/server/routes/api/history/register_history_routes.js b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/settings/register_settings_routes.ts similarity index 62% rename from x-pack/legacy/plugins/watcher/server/routes/api/history/register_history_routes.js rename to x-pack/legacy/plugins/watcher/server/np_ready/routes/api/settings/register_settings_routes.ts index bef26fbb9b26..0b24ec0e90bd 100644 --- a/x-pack/legacy/plugins/watcher/server/routes/api/history/register_history_routes.js +++ b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/settings/register_settings_routes.ts @@ -5,7 +5,8 @@ */ import { registerLoadRoute } from './register_load_route'; +import { RouteDependencies, ServerShim } from '../../../types'; -export function registerHistoryRoutes(server) { - registerLoadRoute(server); +export function registerSettingsRoutes(deps: RouteDependencies, legacy: ServerShim) { + registerLoadRoute(deps, legacy); } diff --git a/x-pack/legacy/plugins/watcher/server/routes/api/watch/action/index.js b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/action/index.ts similarity index 100% rename from x-pack/legacy/plugins/watcher/server/routes/api/watch/action/index.js rename to x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/action/index.ts diff --git a/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/action/register_acknowledge_route.ts b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/action/register_acknowledge_route.ts new file mode 100644 index 000000000000..d0cc0a27e87f --- /dev/null +++ b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/action/register_acknowledge_route.ts @@ -0,0 +1,65 @@ +/* + * 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 { schema } from '@kbn/config-schema'; +import { get } from 'lodash'; +import { RequestHandler } from 'src/core/server'; +import { callWithRequestFactory } from '../../../../lib/call_with_request_factory'; +import { isEsError } from '../../../../lib/is_es_error'; +import { licensePreRoutingFactory } from '../../../../lib/license_pre_routing_factory'; +// @ts-ignore +import { WatchStatus } from '../../../../models/watch_status'; +import { RouteDependencies, ServerShim } from '../../../../types'; + +function acknowledgeAction(callWithRequest: any, watchId: string, actionId: string) { + return callWithRequest('watcher.ackWatch', { + id: watchId, + action: actionId, + }); +} + +export function registerAcknowledgeRoute(deps: RouteDependencies, legacy: ServerShim) { + const handler: RequestHandler = async (ctx, request, response) => { + const callWithRequest = callWithRequestFactory(deps.elasticsearchService, request); + const { watchId, actionId } = request.params; + + try { + const hit = await acknowledgeAction(callWithRequest, watchId, actionId); + const watchStatusJson = get(hit, 'status'); + const json = { + id: watchId, + watchStatusJson, + }; + + const watchStatus = WatchStatus.fromUpstreamJson(json); + return response.ok({ + body: { watchStatus: watchStatus.downstreamJson }, + }); + } catch (e) { + // Case: Error from Elasticsearch JS client + if (isEsError(e)) { + const body = e.statusCode === 404 ? `Watch with id = ${watchId} not found` : e; + return response.customError({ statusCode: e.statusCode, body }); + } + + // Case: default + return response.internalError({ body: e }); + } + }; + + deps.router.put( + { + path: '/api/watcher/watch/{watchId}/action/{actionId}/acknowledge', + validate: { + params: schema.object({ + watchId: schema.string(), + actionId: schema.string(), + }), + }, + }, + licensePreRoutingFactory(legacy, handler) + ); +} diff --git a/x-pack/legacy/plugins/watcher/server/routes/api/watch/action/register_action_routes.js b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/action/register_action_routes.ts similarity index 61% rename from x-pack/legacy/plugins/watcher/server/routes/api/watch/action/register_action_routes.js rename to x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/action/register_action_routes.ts index 6f2c86664420..022c84486793 100644 --- a/x-pack/legacy/plugins/watcher/server/routes/api/watch/action/register_action_routes.js +++ b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/action/register_action_routes.ts @@ -5,7 +5,8 @@ */ import { registerAcknowledgeRoute } from './register_acknowledge_route'; +import { RouteDependencies, ServerShim } from '../../../../types'; -export function registerActionRoutes(server) { - registerAcknowledgeRoute(server); +export function registerActionRoutes(server: RouteDependencies, legacy: ServerShim) { + registerAcknowledgeRoute(server, legacy); } diff --git a/x-pack/legacy/plugins/watcher/server/routes/api/watch/index.js b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/index.ts similarity index 100% rename from x-pack/legacy/plugins/watcher/server/routes/api/watch/index.js rename to x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/index.ts diff --git a/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_activate_route.ts b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_activate_route.ts new file mode 100644 index 000000000000..28c482124aae --- /dev/null +++ b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_activate_route.ts @@ -0,0 +1,66 @@ +/* + * 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 { schema } from '@kbn/config-schema'; +import { RequestHandler } from 'src/core/server'; +import { get } from 'lodash'; +import { callWithRequestFactory } from '../../../lib/call_with_request_factory'; +import { isEsError } from '../../../lib/is_es_error'; +import { licensePreRoutingFactory } from '../../../lib/license_pre_routing_factory'; +import { RouteDependencies, ServerShim } from '../../../types'; +// @ts-ignore +import { WatchStatus } from '../../../models/watch_status'; + +function activateWatch(callWithRequest: any, watchId: string) { + return callWithRequest('watcher.activateWatch', { + id: watchId, + }); +} + +export function registerActivateRoute(deps: RouteDependencies, legacy: ServerShim) { + const handler: RequestHandler = async (ctx, request, response) => { + const callWithRequest = callWithRequestFactory(deps.elasticsearchService, request); + + const { watchId } = request.params; + + try { + const hit = await activateWatch(callWithRequest, watchId); + const watchStatusJson = get(hit, 'status'); + const json = { + id: watchId, + watchStatusJson, + }; + + const watchStatus = WatchStatus.fromUpstreamJson(json); + return response.ok({ + body: { + watchStatus: watchStatus.downstreamJson, + }, + }); + } catch (e) { + // Case: Error from Elasticsearch JS client + if (isEsError(e)) { + const body = e.statusCode === 404 ? `Watch with id = ${watchId} not found` : e; + return response.customError({ statusCode: e.statusCode, body }); + } + + // Case: default + return response.internalError({ body: e }); + } + }; + + deps.router.put( + { + path: '/api/watcher/watch/{watchId}/activate', + validate: { + params: schema.object({ + watchId: schema.string(), + }), + }, + }, + licensePreRoutingFactory(legacy, handler) + ); +} diff --git a/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_deactivate_route.ts b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_deactivate_route.ts new file mode 100644 index 000000000000..ac87066379a2 --- /dev/null +++ b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_deactivate_route.ts @@ -0,0 +1,65 @@ +/* + * 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 { schema } from '@kbn/config-schema'; +import { RequestHandler } from 'src/core/server'; +import { get } from 'lodash'; +import { callWithRequestFactory } from '../../../lib/call_with_request_factory'; +import { isEsError } from '../../../lib/is_es_error'; +import { licensePreRoutingFactory } from '../../../lib/license_pre_routing_factory'; +import { RouteDependencies, ServerShim } from '../../../types'; +// @ts-ignore +import { WatchStatus } from '../../../models/watch_status'; + +function deactivateWatch(callWithRequest: any, watchId: string) { + return callWithRequest('watcher.deactivateWatch', { + id: watchId, + }); +} + +export function registerDeactivateRoute(deps: RouteDependencies, legacy: ServerShim) { + const handler: RequestHandler = async (ctx, request, response) => { + const callWithRequest = callWithRequestFactory(deps.elasticsearchService, request); + + const { watchId } = request.params; + + try { + const hit = await deactivateWatch(callWithRequest, watchId); + const watchStatusJson = get(hit, 'status'); + const json = { + id: watchId, + watchStatusJson, + }; + + const watchStatus = WatchStatus.fromUpstreamJson(json); + return response.ok({ + body: { + watchStatus: watchStatus.downstreamJson, + }, + }); + } catch (e) { + // Case: Error from Elasticsearch JS client + if (isEsError(e)) { + const body = e.statusCode === 404 ? `Watch with id = ${watchId} not found` : e; + return response.customError({ statusCode: e.statusCode, body }); + } + + // Case: default + return response.internalError({ body: e }); + } + }; + + deps.router.put( + { + path: '/api/watcher/watch/{watchId}/deactivate', + validate: { + params: schema.object({ + watchId: schema.string(), + }), + }, + }, + licensePreRoutingFactory(legacy, handler) + ); +} diff --git a/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_delete_route.ts b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_delete_route.ts new file mode 100644 index 000000000000..3402cc283dba --- /dev/null +++ b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_delete_route.ts @@ -0,0 +1,52 @@ +/* + * 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 { schema } from '@kbn/config-schema'; +import { RequestHandler } from 'src/core/server'; +import { callWithRequestFactory } from '../../../lib/call_with_request_factory'; +import { isEsError } from '../../../lib/is_es_error'; +import { licensePreRoutingFactory } from '../../../lib/license_pre_routing_factory'; +import { RouteDependencies, ServerShim } from '../../../types'; + +function deleteWatch(callWithRequest: any, watchId: string) { + return callWithRequest('watcher.deleteWatch', { + id: watchId, + }); +} + +export function registerDeleteRoute(deps: RouteDependencies, legacy: ServerShim) { + const handler: RequestHandler = async (ctx, request, response) => { + const callWithRequest = callWithRequestFactory(deps.elasticsearchService, request); + + const { watchId } = request.params; + + try { + await deleteWatch(callWithRequest, watchId); + return response.noContent(); + } catch (e) { + // Case: Error from Elasticsearch JS client + if (isEsError(e)) { + const body = e.statusCode === 404 ? `Watch with id = ${watchId} not found` : e; + return response.customError({ statusCode: e.statusCode, body }); + } + + // Case: default + return response.internalError({ body: e }); + } + }; + + deps.router.delete( + { + path: '/api/watcher/watch/{watchId}', + validate: { + params: schema.object({ + watchId: schema.string(), + }), + }, + }, + licensePreRoutingFactory(legacy, handler) + ); +} diff --git a/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_execute_route.ts b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_execute_route.ts new file mode 100644 index 000000000000..f3bce228653f --- /dev/null +++ b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_execute_route.ts @@ -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 { schema } from '@kbn/config-schema'; +import { RequestHandler } from 'src/core/server'; +import { get } from 'lodash'; +import { callWithRequestFactory } from '../../../lib/call_with_request_factory'; +import { isEsError } from '../../../lib/is_es_error'; +import { licensePreRoutingFactory } from '../../../lib/license_pre_routing_factory'; + +import { RouteDependencies, ServerShim } from '../../../types'; +// @ts-ignore +import { ExecuteDetails } from '../../../models/execute_details'; +// @ts-ignore +import { Watch } from '../../../models/watch'; +// @ts-ignore +import { WatchHistoryItem } from '../../../models/watch_history_item'; + +function executeWatch(callWithRequest: any, executeDetails: any, watchJson: any) { + const body = executeDetails; + body.watch = watchJson; + + return callWithRequest('watcher.executeWatch', { + body, + }); +} + +export function registerExecuteRoute(deps: RouteDependencies, legacy: ServerShim) { + const handler: RequestHandler = async (ctx, request, response) => { + const callWithRequest = callWithRequestFactory(deps.elasticsearchService, request); + const executeDetails = ExecuteDetails.fromDownstreamJson(request.body.executeDetails); + const watch = Watch.fromDownstreamJson(request.body.watch); + + try { + const hit = await executeWatch(callWithRequest, executeDetails.upstreamJson, watch.watchJson); + const id = get(hit, '_id'); + const watchHistoryItemJson = get(hit, 'watch_record'); + const watchId = get(hit, 'watch_record.watch_id'); + const json = { + id, + watchId, + watchHistoryItemJson, + includeDetails: true, + }; + + const watchHistoryItem = WatchHistoryItem.fromUpstreamJson(json); + return response.ok({ + body: { + watchHistoryItem: watchHistoryItem.downstreamJson, + }, + }); + } catch (e) { + // Case: Error from Elasticsearch JS client + if (isEsError(e)) { + return response.customError({ statusCode: e.statusCode, body: e }); + } + + // Case: default + return response.internalError({ body: e }); + } + }; + + deps.router.put( + { + path: '/api/watcher/watch/execute', + validate: { + body: schema.object({ + executeDetails: schema.object({}, { allowUnknowns: true }), + watch: schema.object({}, { allowUnknowns: true }), + }), + }, + }, + licensePreRoutingFactory(legacy, handler) + ); +} diff --git a/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_history_route.ts b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_history_route.ts new file mode 100644 index 000000000000..e236d7dd642a --- /dev/null +++ b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_history_route.ts @@ -0,0 +1,97 @@ +/* + * 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 { schema } from '@kbn/config-schema'; +import { RequestHandler } from 'src/core/server'; +import { get } from 'lodash'; +import { callWithRequestFactory } from '../../../lib/call_with_request_factory'; +import { fetchAllFromScroll } from '../../../lib/fetch_all_from_scroll'; +import { INDEX_NAMES, ES_SCROLL_SETTINGS } from '../../../../../common/constants'; +import { isEsError } from '../../../lib/is_es_error'; +import { licensePreRoutingFactory } from '../../../lib/license_pre_routing_factory'; +import { RouteDependencies, ServerShim } from '../../../types'; +// @ts-ignore +import { WatchHistoryItem } from '../../../models/watch_history_item'; + +function fetchHistoryItems(callWithRequest: any, watchId: any, startTime: any) { + const params: any = { + index: INDEX_NAMES.WATCHER_HISTORY, + scroll: ES_SCROLL_SETTINGS.KEEPALIVE, + body: { + size: ES_SCROLL_SETTINGS.PAGE_SIZE, + sort: [{ 'result.execution_time': 'desc' }], + query: { + bool: { + must: [{ term: { watch_id: watchId } }], + }, + }, + }, + }; + + // Add time range clause to query if startTime is specified + if (startTime !== 'all') { + const timeRangeQuery = { range: { 'result.execution_time': { gte: startTime } } }; + params.body.query.bool.must.push(timeRangeQuery); + } + + return callWithRequest('search', params).then((response: any) => + fetchAllFromScroll(response, callWithRequest) + ); +} + +export function registerHistoryRoute(deps: RouteDependencies, legacy: ServerShim) { + const handler: RequestHandler = async (ctx, request, response) => { + const callWithRequest = callWithRequestFactory(deps.elasticsearchService, request); + const { watchId } = request.params; + const { startTime } = request.query; + + try { + const hits = await fetchHistoryItems(callWithRequest, watchId, startTime); + const watchHistoryItems = hits.map((hit: any) => { + const id = get(hit, '_id'); + const watchHistoryItemJson = get(hit, '_source'); + + const opts = { includeDetails: false }; + return WatchHistoryItem.fromUpstreamJson( + { + id, + watchId, + watchHistoryItemJson, + }, + opts + ); + }); + + return response.ok({ + body: { + watchHistoryItems: watchHistoryItems.map( + (watchHistoryItem: any) => watchHistoryItem.downstreamJson + ), + }, + }); + } catch (e) { + // Case: Error from Elasticsearch JS client + if (isEsError(e)) { + return response.customError({ statusCode: e.statusCode, body: e }); + } + + // Case: default + return response.internalError({ body: e }); + } + }; + + deps.router.get( + { + path: '/api/watcher/watch/{watchId}/history', + validate: { + params: schema.object({ + watchId: schema.string(), + }), + }, + }, + licensePreRoutingFactory(legacy, handler) + ); +} diff --git a/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_load_route.ts b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_load_route.ts new file mode 100644 index 000000000000..7311ad08f73a --- /dev/null +++ b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_load_route.ts @@ -0,0 +1,69 @@ +/* + * 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 { schema } from '@kbn/config-schema'; +import { RequestHandler } from 'src/core/server'; +import { get } from 'lodash'; +import { callWithRequestFactory } from '../../../lib/call_with_request_factory'; +import { isEsError } from '../../../lib/is_es_error'; +import { licensePreRoutingFactory } from '../../../lib/license_pre_routing_factory'; +// @ts-ignore +import { Watch } from '../../../models/watch'; +import { RouteDependencies, ServerShim } from '../../../types'; + +function fetchWatch(callWithRequest: any, watchId: string) { + return callWithRequest('watcher.getWatch', { + id: watchId, + }); +} + +export function registerLoadRoute(deps: RouteDependencies, legacy: ServerShim) { + const handler: RequestHandler = async (ctx, request, response) => { + const callWithRequest = callWithRequestFactory(deps.elasticsearchService, request); + + const id = request.params.id; + + try { + const hit = await fetchWatch(callWithRequest, id); + const watchJson = get(hit, 'watch'); + const watchStatusJson = get(hit, 'status'); + const json = { + id, + watchJson, + watchStatusJson, + }; + + const watch = Watch.fromUpstreamJson(json, { + throwExceptions: { + Action: false, + }, + }); + return response.ok({ + body: { watch: watch.downstreamJson }, + }); + } catch (e) { + // Case: Error from Elasticsearch JS client + if (isEsError(e)) { + const body = e.statusCode === 404 ? `Watch with id = ${id} not found` : e; + return response.customError({ statusCode: e.statusCode, body }); + } + + // Case: default + return response.internalError({ body: e }); + } + }; + deps.router.get( + { + path: '/api/watcher/watch/{id}', + validate: { + params: schema.object({ + id: schema.string(), + }), + }, + }, + licensePreRoutingFactory(legacy, handler) + ); +} diff --git a/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_save_route.ts b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_save_route.ts new file mode 100644 index 000000000000..5d22392d49ed --- /dev/null +++ b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_save_route.ts @@ -0,0 +1,104 @@ +/* + * 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 { schema } from '@kbn/config-schema'; +import { RequestHandler } from 'src/core/server'; +import { i18n } from '@kbn/i18n'; +import { WATCH_TYPES } from '../../../../../common/constants'; +import { + serializeJsonWatch, + serializeThresholdWatch, +} from '../../../../../common/lib/serialization'; +import { callWithRequestFactory } from '../../../lib/call_with_request_factory'; +import { isEsError } from '../../../lib/is_es_error'; +import { licensePreRoutingFactory } from '../../../lib/license_pre_routing_factory'; +import { RouteDependencies, ServerShim } from '../../../types'; + +function fetchWatch(callWithRequest: any, watchId: string) { + return callWithRequest('watcher.getWatch', { + id: watchId, + }); +} + +function saveWatch(callWithRequest: any, id: string, body: any) { + return callWithRequest('watcher.putWatch', { + id, + body, + }); +} + +export function registerSaveRoute(deps: RouteDependencies, legacy: ServerShim) { + const handler: RequestHandler = async (ctx, request, response) => { + const callWithRequest = callWithRequestFactory(deps.elasticsearchService, request); + const { id } = request.params; + const { type, isNew, ...watchConfig } = request.body; + + // For new watches, verify watch with the same ID doesn't already exist + if (isNew) { + try { + const existingWatch = await fetchWatch(callWithRequest, id); + if (existingWatch.found) { + return response.conflict({ + body: { + message: i18n.translate('xpack.watcher.saveRoute.duplicateWatchIdErrorMessage', { + defaultMessage: "There is already a watch with ID '{watchId}'.", + values: { + watchId: id, + }, + }), + }, + }); + } + } catch (e) { + const es404 = isEsError(e) && e.statusCode === 404; + if (!es404) { + return response.internalError({ body: e }); + } + // Else continue... + } + } + + let serializedWatch; + + switch (type) { + case WATCH_TYPES.JSON: + const { name, watch } = watchConfig; + serializedWatch = serializeJsonWatch(name, watch); + break; + + case WATCH_TYPES.THRESHOLD: + serializedWatch = serializeThresholdWatch(watchConfig); + break; + } + + try { + // Create new watch + await saveWatch(callWithRequest, id, serializedWatch); + return response.noContent(); + } catch (e) { + // Case: Error from Elasticsearch JS client + if (isEsError(e)) { + return response.customError({ statusCode: e.statusCode, body: e }); + } + + // Case: default + return response.internalError({ body: e }); + } + }; + + deps.router.put( + { + path: '/api/watcher/watch/{id}', + validate: { + params: schema.object({ + id: schema.string(), + }), + body: schema.object({}, { allowUnknowns: true }), + }, + }, + licensePreRoutingFactory(legacy, handler) + ); +} diff --git a/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_visualize_route.ts b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_visualize_route.ts new file mode 100644 index 000000000000..d07a264b0b2b --- /dev/null +++ b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_visualize_route.ts @@ -0,0 +1,70 @@ +/* + * 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 { schema } from '@kbn/config-schema'; +import { RequestHandler } from 'src/core/server'; +import { callWithRequestFactory } from '../../../lib/call_with_request_factory'; +import { isEsError } from '../../../lib/is_es_error'; +import { licensePreRoutingFactory } from '../../../lib/license_pre_routing_factory'; +import { RouteDependencies, ServerShim } from '../../../types'; + +// @ts-ignore +import { Watch } from '../../../models/watch'; +// @ts-ignore +import { VisualizeOptions } from '../../../models/visualize_options'; + +function fetchVisualizeData(callWithRequest: any, index: any, body: any) { + const params = { + index, + body, + ignoreUnavailable: true, + allowNoIndices: true, + ignore: [404], + }; + + return callWithRequest('search', params); +} + +export function registerVisualizeRoute(deps: RouteDependencies, legacy: ServerShim) { + const handler: RequestHandler = async (ctx, request, response) => { + const callWithRequest = callWithRequestFactory(deps.elasticsearchService, request); + const watch = Watch.fromDownstreamJson(request.body.watch); + const options = VisualizeOptions.fromDownstreamJson(request.body.options); + const body = watch.getVisualizeQuery(options); + + try { + const hits = await fetchVisualizeData(callWithRequest, watch.index, body); + const visualizeData = watch.formatVisualizeData(hits); + + return response.ok({ + body: { + visualizeData, + }, + }); + } catch (e) { + // Case: Error from Elasticsearch JS client + if (isEsError(e)) { + return response.customError({ statusCode: e.statusCode, body: e }); + } + + // Case: default + return response.internalError({ body: e }); + } + }; + + deps.router.post( + { + path: '/api/watcher/watch/visualize', + validate: { + body: schema.object({ + watch: schema.object({}, { allowUnknowns: true }), + options: schema.object({}, { allowUnknowns: true }), + }), + }, + }, + licensePreRoutingFactory(legacy, handler) + ); +} diff --git a/x-pack/legacy/plugins/watcher/server/routes/api/watch/register_watch_routes.js b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_watch_routes.ts similarity index 62% rename from x-pack/legacy/plugins/watcher/server/routes/api/watch/register_watch_routes.js rename to x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_watch_routes.ts index 8419f6db7f65..5ecbf3e0d2b4 100644 --- a/x-pack/legacy/plugins/watcher/server/routes/api/watch/register_watch_routes.js +++ b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_watch_routes.ts @@ -13,15 +13,16 @@ import { registerActivateRoute } from './register_activate_route'; import { registerDeactivateRoute } from './register_deactivate_route'; import { registerVisualizeRoute } from './register_visualize_route'; import { registerActionRoutes } from './action'; +import { RouteDependencies, ServerShim } from '../../../types'; -export function registerWatchRoutes(server) { - registerDeleteRoute(server); - registerExecuteRoute(server); - registerLoadRoute(server); - registerSaveRoute(server); - registerHistoryRoute(server); - registerActivateRoute(server); - registerDeactivateRoute(server); - registerActionRoutes(server); - registerVisualizeRoute(server); +export function registerWatchRoutes(deps: RouteDependencies, legacy: ServerShim) { + registerDeleteRoute(deps, legacy); + registerExecuteRoute(deps, legacy); + registerLoadRoute(deps, legacy); + registerSaveRoute(deps, legacy); + registerHistoryRoute(deps, legacy); + registerActivateRoute(deps, legacy); + registerDeactivateRoute(deps, legacy); + registerActionRoutes(deps, legacy); + registerVisualizeRoute(deps, legacy); } diff --git a/x-pack/legacy/plugins/watcher/server/routes/api/watches/index.js b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watches/index.ts similarity index 100% rename from x-pack/legacy/plugins/watcher/server/routes/api/watches/index.js rename to x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watches/index.ts diff --git a/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watches/register_delete_route.ts b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watches/register_delete_route.ts new file mode 100644 index 000000000000..29c539a0de13 --- /dev/null +++ b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watches/register_delete_route.ts @@ -0,0 +1,63 @@ +/* + * 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 { schema } from '@kbn/config-schema'; +import { RequestHandler } from 'src/core/server'; +import { callWithRequestFactory } from '../../../lib/call_with_request_factory'; +import { licensePreRoutingFactory } from '../../../lib/license_pre_routing_factory'; +import { RouteDependencies, ServerShim } from '../../../types'; + +function deleteWatches(callWithRequest: any, watchIds: string[]) { + const deletePromises = watchIds.map(watchId => { + return callWithRequest('watcher.deleteWatch', { + id: watchId, + }) + .then((success: Array<{ _id: string }>) => ({ success })) + .catch((error: Array<{ _id: string }>) => ({ error })); + }); + + return Promise.all(deletePromises).then(results => { + const errors: Error[] = []; + const successes: boolean[] = []; + results.forEach(({ success, error }) => { + if (success) { + successes.push(success._id); + } else if (error) { + errors.push(error._id); + } + }); + + return { + successes, + errors, + }; + }); +} + +export function registerDeleteRoute(deps: RouteDependencies, legacy: ServerShim) { + const handler: RequestHandler = async (ctx, request, response) => { + const callWithRequest = callWithRequestFactory(deps.elasticsearchService, request); + + try { + const results = await deleteWatches(callWithRequest, request.body.watchIds); + return response.ok({ body: { results } }); + } catch (e) { + return response.internalError({ body: e }); + } + }; + + deps.router.post( + { + path: '/api/watcher/watches/delete', + validate: { + body: schema.object({ + watchIds: schema.arrayOf(schema.string()), + }), + }, + }, + licensePreRoutingFactory(legacy, handler) + ); +} diff --git a/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watches/register_list_route.ts b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watches/register_list_route.ts new file mode 100644 index 000000000000..b94c29e0f989 --- /dev/null +++ b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watches/register_list_route.ts @@ -0,0 +1,86 @@ +/* + * 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 { RequestHandler } from 'src/core/server'; +import { get } from 'lodash'; +import { callWithRequestFactory } from '../../../lib/call_with_request_factory'; +import { fetchAllFromScroll } from '../../../lib/fetch_all_from_scroll'; +import { INDEX_NAMES, ES_SCROLL_SETTINGS } from '../../../../../common/constants'; +import { isEsError } from '../../../lib/is_es_error'; +import { licensePreRoutingFactory } from '../../../lib/license_pre_routing_factory'; +import { RouteDependencies, ServerShim } from '../../../types'; +// @ts-ignore +import { Watch } from '../../../models/watch'; + +function fetchWatches(callWithRequest: any) { + const params = { + index: INDEX_NAMES.WATCHES, + scroll: ES_SCROLL_SETTINGS.KEEPALIVE, + body: { + size: ES_SCROLL_SETTINGS.PAGE_SIZE, + }, + ignore: [404], + }; + + return callWithRequest('search', params).then((response: any) => + fetchAllFromScroll(response, callWithRequest) + ); +} + +export function registerListRoute(deps: RouteDependencies, legacy: ServerShim) { + const handler: RequestHandler = async (ctx, request, response) => { + const callWithRequest = callWithRequestFactory(deps.elasticsearchService, request); + + try { + const hits = await fetchWatches(callWithRequest); + const watches = hits.map((hit: any) => { + const id = get(hit, '_id'); + const watchJson = get(hit, '_source'); + const watchStatusJson = get(hit, '_source.status'); + + return Watch.fromUpstreamJson( + { + id, + watchJson, + watchStatusJson, + }, + { + throwExceptions: { + Action: false, + }, + } + ); + }); + + return response.ok({ + body: { + watches: watches.map((watch: any) => watch.downstreamJson), + }, + }); + } catch (e) { + // Case: Error from Elasticsearch JS client + if (isEsError(e)) { + return response.customError({ + statusCode: e.statusCode, + body: { + message: e.message, + }, + }); + } + + // Case: default + return response.internalError({ body: e }); + } + }; + + deps.router.get( + { + path: '/api/watcher/watches', + validate: false, + }, + licensePreRoutingFactory(legacy, handler) + ); +} diff --git a/x-pack/legacy/plugins/watcher/server/routes/api/watches/register_watches_routes.js b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watches/register_watches_routes.ts similarity index 62% rename from x-pack/legacy/plugins/watcher/server/routes/api/watches/register_watches_routes.js rename to x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watches/register_watches_routes.ts index 5f7ae6a5935b..dd5f55078e59 100644 --- a/x-pack/legacy/plugins/watcher/server/routes/api/watches/register_watches_routes.js +++ b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watches/register_watches_routes.ts @@ -6,8 +6,9 @@ import { registerListRoute } from './register_list_route'; import { registerDeleteRoute } from './register_delete_route'; +import { RouteDependencies, ServerShim } from '../../../types'; -export function registerWatchesRoutes(server) { - registerListRoute(server); - registerDeleteRoute(server); +export function registerWatchesRoutes(deps: RouteDependencies, legacy: ServerShim) { + registerListRoute(deps, legacy); + registerDeleteRoute(deps, legacy); } diff --git a/x-pack/legacy/plugins/watcher/server/np_ready/types.ts b/x-pack/legacy/plugins/watcher/server/np_ready/types.ts new file mode 100644 index 000000000000..1b566332befd --- /dev/null +++ b/x-pack/legacy/plugins/watcher/server/np_ready/types.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IRouter, ElasticsearchServiceSetup, IClusterClient } from 'src/core/server'; +import { XPackMainPlugin } from '../../../xpack_main/xpack_main'; + +export interface ServerShim { + route: any; + plugins: { + xpack_main: XPackMainPlugin; + watcher: any; + }; +} + +export interface RouteDependencies { + router: IRouter; + elasticsearchService: ElasticsearchServiceSetup; + elasticsearch: IClusterClient; +} diff --git a/x-pack/legacy/plugins/watcher/server/routes/api/fields/register_list_route.js b/x-pack/legacy/plugins/watcher/server/routes/api/fields/register_list_route.js deleted file mode 100644 index 7d45d3a2aa60..000000000000 --- a/x-pack/legacy/plugins/watcher/server/routes/api/fields/register_list_route.js +++ /dev/null @@ -1,60 +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 { callWithRequestFactory } from '../../../lib/call_with_request_factory'; -import { isEsErrorFactory } from '../../../lib/is_es_error_factory'; -import { wrapEsError, wrapUnknownError } from '../../../lib/error_wrappers'; -import { licensePreRoutingFactory } from '../../../lib/license_pre_routing_factory'; -import { Fields } from '../../../models/fields'; - -function fetchFields(callWithRequest, indexes) { - const params = { - index: indexes, - fields: ['*'], - ignoreUnavailable: true, - allowNoIndices: true, - ignore: 404 - }; - - return callWithRequest('fieldCaps', params); -} - -export function registerListRoute(server) { - const isEsError = isEsErrorFactory(server); - const licensePreRouting = licensePreRoutingFactory(server); - - server.route({ - path: '/api/watcher/fields', - method: 'POST', - handler: (request) => { - const callWithRequest = callWithRequestFactory(server, request); - const { indexes } = request.payload; - - return fetchFields(callWithRequest, indexes) - .then(response => { - const json = (response.status === 404) - ? { fields: [] } - : response; - - const fields = Fields.fromUpstreamJson(json); - - return fields.downstreamJson; - }) - .catch(err => { - // Case: Error from Elasticsearch JS client - if (isEsError(err)) { - throw wrapEsError(err); - } - - // Case: default - throw wrapUnknownError(err); - }); - }, - config: { - pre: [ licensePreRouting ] - } - }); -} diff --git a/x-pack/legacy/plugins/watcher/server/routes/api/history/index.js b/x-pack/legacy/plugins/watcher/server/routes/api/history/index.js deleted file mode 100644 index 9a66353c742b..000000000000 --- a/x-pack/legacy/plugins/watcher/server/routes/api/history/index.js +++ /dev/null @@ -1,7 +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. - */ - -export { registerHistoryRoutes } from './register_history_routes'; diff --git a/x-pack/legacy/plugins/watcher/server/routes/api/history/register_load_route.js b/x-pack/legacy/plugins/watcher/server/routes/api/history/register_load_route.js deleted file mode 100644 index 1d34be56fcef..000000000000 --- a/x-pack/legacy/plugins/watcher/server/routes/api/history/register_load_route.js +++ /dev/null @@ -1,78 +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 { get } from 'lodash'; -import { callWithRequestFactory } from '../../../lib/call_with_request_factory'; -import { WatchHistoryItem } from '../../../models/watch_history_item'; -import { isEsErrorFactory } from '../../../lib/is_es_error_factory'; -import { wrapEsError, wrapUnknownError, wrapCustomError } from '../../../lib/error_wrappers'; -import { INDEX_NAMES } from '../../../../common/constants'; -import { licensePreRoutingFactory } from'../../../lib/license_pre_routing_factory'; - -function fetchHistoryItem(callWithRequest, watchHistoryItemId) { - return callWithRequest('search', { - index: INDEX_NAMES.WATCHER_HISTORY, - body: { - query: { - bool: { - must: [ - { term: { '_id': watchHistoryItemId } }, - ] - } - } - } - }); -} - -export function registerLoadRoute(server) { - const isEsError = isEsErrorFactory(server); - const licensePreRouting = licensePreRoutingFactory(server); - - server.route({ - path: '/api/watcher/history/{id}', - method: 'GET', - handler: (request) => { - const callWithRequest = callWithRequestFactory(server, request); - const id = request.params.id; - - return fetchHistoryItem(callWithRequest, id) - .then((responseFromES) => { - const hit = get(responseFromES, 'hits.hits[0]'); - if (!hit) { - throw wrapCustomError( - new Error(`Watch History Item with id = ${id} not found`), 404 - ); - } - - const watchHistoryItemJson = get(hit, '_source'); - const watchId = get(hit, '_source.watch_id'); - const json = { - id, - watchId, - watchHistoryItemJson, - includeDetails: true - }; - - const watchHistoryItem = WatchHistoryItem.fromUpstreamJson(json); - return { - watchHistoryItem: watchHistoryItem.downstreamJson - }; - }) - .catch(err => { - // Case: Error from Elasticsearch JS client - if (isEsError(err)) { - throw wrapEsError(err); - } - - // Case: default - throw wrapUnknownError(err); - }); - }, - config: { - pre: [ licensePreRouting ] - } - }); -} diff --git a/x-pack/legacy/plugins/watcher/server/routes/api/indices/register_get_route.js b/x-pack/legacy/plugins/watcher/server/routes/api/indices/register_get_route.js deleted file mode 100644 index 86de6f3da7ad..000000000000 --- a/x-pack/legacy/plugins/watcher/server/routes/api/indices/register_get_route.js +++ /dev/null @@ -1,89 +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 { reduce, size } from 'lodash'; -import { callWithRequestFactory } from '../../../lib/call_with_request_factory'; -import { isEsErrorFactory } from '../../../lib/is_es_error_factory'; -import { wrapEsError, wrapUnknownError } from '../../../lib/error_wrappers'; -import { licensePreRoutingFactory } from'../../../lib/license_pre_routing_factory'; - -function getIndexNamesFromAliasesResponse(json) { - return reduce(json, (list, { aliases }, indexName) => { - list.push(indexName); - if (size(aliases) > 0) { - list.push(...Object.keys(aliases)); - } - return list; - }, []); -} - -function getIndices(callWithRequest, pattern, limit = 10) { - return callWithRequest('indices.getAlias', { - index: pattern, - ignore: [404] - }) - .then(aliasResult => { - if (aliasResult.status !== 404) { - const indicesFromAliasResponse = getIndexNamesFromAliasesResponse(aliasResult); - return indicesFromAliasResponse.slice(0, limit); - } - - const params = { - index: pattern, - ignore: [404], - body: { - size: 0, // no hits - aggs: { - indices: { - terms: { - field: '_index', - size: limit, - } - } - } - } - }; - - return callWithRequest('search', params) - .then(response => { - if (response.status === 404 || !response.aggregations) { - return []; - } - return response.aggregations.indices.buckets.map(bucket => bucket.key); - }); - }); -} - -export function registerGetRoute(server) { - const isEsError = isEsErrorFactory(server); - const licensePreRouting = licensePreRoutingFactory(server); - - server.route({ - path: '/api/watcher/indices', - method: 'POST', - handler: (request) => { - const callWithRequest = callWithRequestFactory(server, request); - const { pattern } = request.payload; - - return getIndices(callWithRequest, pattern) - .then(indices => { - return { indices }; - }) - .catch(err => { - // Case: Error from Elasticsearch JS client - if (isEsError(err)) { - throw wrapEsError(err); - } - - // Case: default - throw wrapUnknownError(err); - }); - }, - config: { - pre: [ licensePreRouting ] - } - }); -} diff --git a/x-pack/legacy/plugins/watcher/server/routes/api/settings/register_load_route.js b/x-pack/legacy/plugins/watcher/server/routes/api/settings/register_load_route.js deleted file mode 100644 index 65c961c8c82f..000000000000 --- a/x-pack/legacy/plugins/watcher/server/routes/api/settings/register_load_route.js +++ /dev/null @@ -1,47 +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 { callWithInternalUserFactory } from '../../../lib/call_with_internal_user_factory'; -import { isEsErrorFactory } from '../../../lib/is_es_error_factory'; -import { wrapEsError, wrapUnknownError } from '../../../lib/error_wrappers'; -import { licensePreRoutingFactory } from'../../../lib/license_pre_routing_factory'; -import { Settings } from '../../../models/settings'; - -function fetchClusterSettings(callWithInternalUser) { - return callWithInternalUser('cluster.getSettings', { - includeDefaults: true, - filterPath: '**.xpack.notification' - }); -} - -export function registerLoadRoute(server) { - const isEsError = isEsErrorFactory(server); - const licensePreRouting = licensePreRoutingFactory(server); - const callWithInternalUser = callWithInternalUserFactory(server); - - server.route({ - path: '/api/watcher/settings', - method: 'GET', - handler: () => { - return fetchClusterSettings(callWithInternalUser) - .then((settings) => { - return Settings.fromUpstreamJson(settings).downstreamJson; - }) - .catch(err => { - // Case: Error from Elasticsearch JS client - if (isEsError(err)) { - throw wrapEsError(err); - } - - // Case: default - throw wrapUnknownError(err); - }); - }, - config: { - pre: [ licensePreRouting ] - } - }); -} diff --git a/x-pack/legacy/plugins/watcher/server/routes/api/settings/register_settings_routes.js b/x-pack/legacy/plugins/watcher/server/routes/api/settings/register_settings_routes.js deleted file mode 100644 index eefb320e9b1d..000000000000 --- a/x-pack/legacy/plugins/watcher/server/routes/api/settings/register_settings_routes.js +++ /dev/null @@ -1,11 +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 { registerLoadRoute } from './register_load_route'; - -export function registerSettingsRoutes(server) { - registerLoadRoute(server); -} diff --git a/x-pack/legacy/plugins/watcher/server/routes/api/watch/action/register_acknowledge_route.js b/x-pack/legacy/plugins/watcher/server/routes/api/watch/action/register_acknowledge_route.js deleted file mode 100644 index ffecebf805cf..000000000000 --- a/x-pack/legacy/plugins/watcher/server/routes/api/watch/action/register_acknowledge_route.js +++ /dev/null @@ -1,63 +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 { get } from 'lodash'; -import { callWithRequestFactory } from '../../../../lib/call_with_request_factory'; -import { isEsErrorFactory } from '../../../../lib/is_es_error_factory'; -import { wrapEsError, wrapUnknownError } from '../../../../lib/error_wrappers'; -import { WatchStatus } from '../../../../models/watch_status'; -import { licensePreRoutingFactory } from'../../../../lib/license_pre_routing_factory'; - -export function registerAcknowledgeRoute(server) { - - const isEsError = isEsErrorFactory(server); - const licensePreRouting = licensePreRoutingFactory(server); - - server.route({ - path: '/api/watcher/watch/{watchId}/action/{actionId}/acknowledge', - method: 'PUT', - handler: (request) => { - const callWithRequest = callWithRequestFactory(server, request); - const { watchId, actionId } = request.params; - - return acknowledgeAction(callWithRequest, watchId, actionId) - .then(hit => { - const watchStatusJson = get(hit, 'status'); - const json = { - id: watchId, - watchStatusJson: watchStatusJson - }; - - const watchStatus = WatchStatus.fromUpstreamJson(json); - return { - watchStatus: watchStatus.downstreamJson - }; - }) - .catch(err => { - // Case: Error from Elasticsearch JS client - if (isEsError(err)) { - const statusCodeToMessageMap = { - 404: `Watch with id = ${watchId} not found` - }; - throw wrapEsError(err, statusCodeToMessageMap); - } - - // Case: default - throw wrapUnknownError(err); - }); - }, - config: { - pre: [ licensePreRouting ] - } - }); -} - -function acknowledgeAction(callWithRequest, watchId, actionId) { - return callWithRequest('watcher.ackWatch', { - id: watchId, - action: actionId - }); -} diff --git a/x-pack/legacy/plugins/watcher/server/routes/api/watch/register_activate_route.js b/x-pack/legacy/plugins/watcher/server/routes/api/watch/register_activate_route.js deleted file mode 100644 index ea669a16a017..000000000000 --- a/x-pack/legacy/plugins/watcher/server/routes/api/watch/register_activate_route.js +++ /dev/null @@ -1,63 +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 { get } from 'lodash'; -import { callWithRequestFactory } from '../../../lib/call_with_request_factory'; -import { isEsErrorFactory } from '../../../lib/is_es_error_factory'; -import { wrapEsError, wrapUnknownError } from '../../../lib/error_wrappers'; -import { licensePreRoutingFactory } from'../../../lib/license_pre_routing_factory'; -import { WatchStatus } from '../../../models/watch_status'; - -function activateWatch(callWithRequest, watchId) { - return callWithRequest('watcher.activateWatch', { - id: watchId - }); -} - -export function registerActivateRoute(server) { - - const isEsError = isEsErrorFactory(server); - const licensePreRouting = licensePreRoutingFactory(server); - - server.route({ - path: '/api/watcher/watch/{watchId}/activate', - method: 'PUT', - handler: (request) => { - const callWithRequest = callWithRequestFactory(server, request); - - const { watchId } = request.params; - - return activateWatch(callWithRequest, watchId) - .then(hit => { - const watchStatusJson = get(hit, 'status'); - const json = { - id: watchId, - watchStatusJson: watchStatusJson - }; - - const watchStatus = WatchStatus.fromUpstreamJson(json); - return { - watchStatus: watchStatus.downstreamJson - }; - }) - .catch(err => { - // Case: Error from Elasticsearch JS client - if (isEsError(err)) { - const statusCodeToMessageMap = { - 404: `Watch with id = ${watchId} not found` - }; - throw wrapEsError(err, statusCodeToMessageMap); - } - - // Case: default - throw wrapUnknownError(err); - }); - }, - config: { - pre: [ licensePreRouting ] - } - }); -} diff --git a/x-pack/legacy/plugins/watcher/server/routes/api/watch/register_deactivate_route.js b/x-pack/legacy/plugins/watcher/server/routes/api/watch/register_deactivate_route.js deleted file mode 100644 index 2411290e2034..000000000000 --- a/x-pack/legacy/plugins/watcher/server/routes/api/watch/register_deactivate_route.js +++ /dev/null @@ -1,63 +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 { get } from 'lodash'; -import { callWithRequestFactory } from '../../../lib/call_with_request_factory'; -import { isEsErrorFactory } from '../../../lib/is_es_error_factory'; -import { wrapEsError, wrapUnknownError } from '../../../lib/error_wrappers'; -import { licensePreRoutingFactory } from'../../../lib/license_pre_routing_factory'; -import { WatchStatus } from '../../../models/watch_status'; - -function deactivateWatch(callWithRequest, watchId) { - return callWithRequest('watcher.deactivateWatch', { - id: watchId - }); -} - -export function registerDeactivateRoute(server) { - - const isEsError = isEsErrorFactory(server); - const licensePreRouting = licensePreRoutingFactory(server); - - server.route({ - path: '/api/watcher/watch/{watchId}/deactivate', - method: 'PUT', - handler: (request) => { - const callWithRequest = callWithRequestFactory(server, request); - - const { watchId } = request.params; - - return deactivateWatch(callWithRequest, watchId) - .then(hit => { - const watchStatusJson = get(hit, 'status'); - const json = { - id: watchId, - watchStatusJson: watchStatusJson - }; - - const watchStatus = WatchStatus.fromUpstreamJson(json); - return { - watchStatus: watchStatus.downstreamJson - }; - }) - .catch(err => { - // Case: Error from Elasticsearch JS client - if (isEsError(err)) { - const statusCodeToMessageMap = { - 404: `Watch with id = ${watchId} not found` - }; - throw wrapEsError(err, statusCodeToMessageMap); - } - - // Case: default - throw wrapUnknownError(err); - }); - }, - config: { - pre: [ licensePreRouting ] - } - }); -} diff --git a/x-pack/legacy/plugins/watcher/server/routes/api/watch/register_delete_route.js b/x-pack/legacy/plugins/watcher/server/routes/api/watch/register_delete_route.js deleted file mode 100644 index dc3b015dffa9..000000000000 --- a/x-pack/legacy/plugins/watcher/server/routes/api/watch/register_delete_route.js +++ /dev/null @@ -1,50 +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 { callWithRequestFactory } from '../../../lib/call_with_request_factory'; -import { isEsErrorFactory } from '../../../lib/is_es_error_factory'; -import { wrapEsError, wrapUnknownError } from '../../../lib/error_wrappers'; -import { licensePreRoutingFactory } from'../../../lib/license_pre_routing_factory'; - -function deleteWatch(callWithRequest, watchId) { - return callWithRequest('watcher.deleteWatch', { - id: watchId - }); -} - -export function registerDeleteRoute(server) { - - const isEsError = isEsErrorFactory(server); - const licensePreRouting = licensePreRoutingFactory(server); - - server.route({ - path: '/api/watcher/watch/{watchId}', - method: 'DELETE', - handler: (request, h) => { - const callWithRequest = callWithRequestFactory(server, request); - - const { watchId } = request.params; - - return deleteWatch(callWithRequest, watchId) - .then(() => h.response().code(204)) - .catch(err => { - // Case: Error from Elasticsearch JS client - if (isEsError(err)) { - const statusCodeToMessageMap = { - 404: `Watch with id = ${watchId} not found` - }; - throw wrapEsError(err, statusCodeToMessageMap); - } - - // Case: default - throw wrapUnknownError(err); - }); - }, - config: { - pre: [ licensePreRouting ] - } - }); -} diff --git a/x-pack/legacy/plugins/watcher/server/routes/api/watch/register_execute_route.js b/x-pack/legacy/plugins/watcher/server/routes/api/watch/register_execute_route.js deleted file mode 100644 index f37882914728..000000000000 --- a/x-pack/legacy/plugins/watcher/server/routes/api/watch/register_execute_route.js +++ /dev/null @@ -1,69 +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 { get } from 'lodash'; -import { callWithRequestFactory } from '../../../lib/call_with_request_factory'; -import { ExecuteDetails } from '../../../models/execute_details'; -import { Watch } from '../../../models/watch'; -import { WatchHistoryItem } from '../../../models/watch_history_item'; -import { isEsErrorFactory } from '../../../lib/is_es_error_factory'; -import { wrapEsError, wrapUnknownError } from '../../../lib/error_wrappers'; -import { licensePreRoutingFactory } from'../../../lib/license_pre_routing_factory'; - -function executeWatch(callWithRequest, executeDetails, watchJson) { - const body = executeDetails; - body.watch = watchJson; - - return callWithRequest('watcher.executeWatch', { - body - }); -} - -export function registerExecuteRoute(server) { - const isEsError = isEsErrorFactory(server); - const licensePreRouting = licensePreRoutingFactory(server); - - server.route({ - path: '/api/watcher/watch/execute', - method: 'PUT', - handler: (request) => { - const callWithRequest = callWithRequestFactory(server, request); - const executeDetails = ExecuteDetails.fromDownstreamJson(request.payload.executeDetails); - const watch = Watch.fromDownstreamJson(request.payload.watch); - - return executeWatch(callWithRequest, executeDetails.upstreamJson, watch.watchJson) - .then((hit) => { - const id = get(hit, '_id'); - const watchHistoryItemJson = get(hit, 'watch_record'); - const watchId = get(hit, 'watch_record.watch_id'); - const json = { - id, - watchId, - watchHistoryItemJson, - includeDetails: true - }; - - const watchHistoryItem = WatchHistoryItem.fromUpstreamJson(json); - return { - watchHistoryItem: watchHistoryItem.downstreamJson - }; - }) - .catch(err => { - - // Case: Error from Elasticsearch JS client - if (isEsError(err)) { - throw wrapEsError(err); - } - - // Case: default - throw wrapUnknownError(err); - }); - }, - config: { - pre: [ licensePreRouting ] - } - }); -} diff --git a/x-pack/legacy/plugins/watcher/server/routes/api/watch/register_history_route.js b/x-pack/legacy/plugins/watcher/server/routes/api/watch/register_history_route.js deleted file mode 100644 index 702cf8a2b64e..000000000000 --- a/x-pack/legacy/plugins/watcher/server/routes/api/watch/register_history_route.js +++ /dev/null @@ -1,89 +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 { get } from 'lodash'; -import { callWithRequestFactory } from '../../../lib/call_with_request_factory'; -import { fetchAllFromScroll } from '../../../lib/fetch_all_from_scroll'; -import { INDEX_NAMES, ES_SCROLL_SETTINGS } from '../../../../common/constants'; -import { WatchHistoryItem } from '../../../models/watch_history_item'; -import { isEsErrorFactory } from '../../../lib/is_es_error_factory'; -import { wrapEsError, wrapUnknownError } from '../../../lib/error_wrappers'; -import { licensePreRoutingFactory } from'../../../lib/license_pre_routing_factory'; - -function fetchHistoryItems(callWithRequest, watchId, startTime) { - const params = { - index: INDEX_NAMES.WATCHER_HISTORY, - scroll: ES_SCROLL_SETTINGS.KEEPALIVE, - body: { - size: ES_SCROLL_SETTINGS.PAGE_SIZE, - sort: [ - { 'result.execution_time': 'desc' } - ], - query: { - bool: { - must: [ - { term: { 'watch_id': watchId } }, - ] - } - } - } - }; - - // Add time range clause to query if startTime is specified - if (startTime !== 'all') { - const timeRangeQuery = { range: { 'result.execution_time': { gte: startTime } } }; - params.body.query.bool.must.push(timeRangeQuery); - } - - return callWithRequest('search', params) - .then(response => fetchAllFromScroll(response, callWithRequest)); -} - -export function registerHistoryRoute(server) { - const isEsError = isEsErrorFactory(server); - const licensePreRouting = licensePreRoutingFactory(server); - - server.route({ - path: '/api/watcher/watch/{watchId}/history', - method: 'GET', - handler: (request) => { - const callWithRequest = callWithRequestFactory(server, request); - const { watchId } = request.params; - const { startTime } = request.query; - - return fetchHistoryItems(callWithRequest, watchId, startTime) - .then(hits => { - const watchHistoryItems = hits.map(hit => { - const id = get(hit, '_id'); - const watchHistoryItemJson = get(hit, '_source'); - - const opts = { includeDetails: false }; - return WatchHistoryItem.fromUpstreamJson({ - id, - watchId, - watchHistoryItemJson - }, opts); - }); - - return { - watchHistoryItems: watchHistoryItems.map(watchHistoryItem => watchHistoryItem.downstreamJson) - }; - }) - .catch(err => { - // Case: Error from Elasticsearch JS client - if (isEsError(err)) { - throw wrapEsError(err); - } - - // Case: default - throw wrapUnknownError(err); - }); - }, - config: { - pre: [ licensePreRouting ] - } - }); -} diff --git a/x-pack/legacy/plugins/watcher/server/routes/api/watch/register_load_route.js b/x-pack/legacy/plugins/watcher/server/routes/api/watch/register_load_route.js deleted file mode 100644 index e5210dbff356..000000000000 --- a/x-pack/legacy/plugins/watcher/server/routes/api/watch/register_load_route.js +++ /dev/null @@ -1,68 +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 { callWithRequestFactory } from '../../../lib/call_with_request_factory'; -import { get } from 'lodash'; -import { Watch } from '../../../models/watch'; -import { isEsErrorFactory } from '../../../lib/is_es_error_factory'; -import { wrapEsError, wrapUnknownError } from '../../../lib/error_wrappers'; -import { licensePreRoutingFactory } from'../../../lib/license_pre_routing_factory'; - -function fetchWatch(callWithRequest, watchId) { - return callWithRequest('watcher.getWatch', { - id: watchId - }); -} - -export function registerLoadRoute(server) { - const isEsError = isEsErrorFactory(server); - const licensePreRouting = licensePreRoutingFactory(server); - - server.route({ - path: '/api/watcher/watch/{id}', - method: 'GET', - handler: (request) => { - const callWithRequest = callWithRequestFactory(server, request); - - const id = request.params.id; - - return fetchWatch(callWithRequest, id) - .then(hit => { - const watchJson = get(hit, 'watch'); - const watchStatusJson = get(hit, 'status'); - const json = { - id, - watchJson, - watchStatusJson, - }; - - const watch = Watch.fromUpstreamJson(json, { - throwExceptions: { - Action: false, - }, - }); - return { - watch: watch.downstreamJson - }; - }) - .catch(err => { - // Case: Error from Elasticsearch JS client - if (isEsError(err)) { - const statusCodeToMessageMap = { - 404: `Watch with id = ${id} not found`, - }; - throw wrapEsError(err, statusCodeToMessageMap); - } - - // Case: default - throw wrapUnknownError(err); - }); - }, - config: { - pre: [ licensePreRouting ] - } - }); -} diff --git a/x-pack/legacy/plugins/watcher/server/routes/api/watch/register_save_route.js b/x-pack/legacy/plugins/watcher/server/routes/api/watch/register_save_route.js deleted file mode 100644 index 3cbb0a4e1cc4..000000000000 --- a/x-pack/legacy/plugins/watcher/server/routes/api/watch/register_save_route.js +++ /dev/null @@ -1,94 +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 { WATCH_TYPES } from '../../../../common/constants'; -import { serializeJsonWatch, serializeThresholdWatch } from '../../../../common/lib/serialization'; -import { callWithRequestFactory } from '../../../lib/call_with_request_factory'; -import { isEsErrorFactory } from '../../../lib/is_es_error_factory'; -import { wrapEsError, wrapUnknownError, wrapCustomError } from '../../../lib/error_wrappers'; -import { licensePreRoutingFactory } from'../../../lib/license_pre_routing_factory'; -import { i18n } from '@kbn/i18n'; - -function fetchWatch(callWithRequest, watchId) { - return callWithRequest('watcher.getWatch', { - id: watchId - }); -} - -function saveWatch(callWithRequest, id, body) { - return callWithRequest('watcher.putWatch', { - id, - body, - }); -} - -export function registerSaveRoute(server) { - const isEsError = isEsErrorFactory(server); - const licensePreRouting = licensePreRoutingFactory(server); - - server.route({ - path: '/api/watcher/watch/{id}', - method: 'PUT', - handler: async (request) => { - const callWithRequest = callWithRequestFactory(server, request); - const { id, type, isNew, ...watchConfig } = request.payload; - - // For new watches, verify watch with the same ID doesn't already exist - if (isNew) { - const conflictError = wrapCustomError( - new Error(i18n.translate('xpack.watcher.saveRoute.duplicateWatchIdErrorMessage', { - defaultMessage: 'There is already a watch with ID \'{watchId}\'.', - values: { - watchId: id, - } - })), - 409 - ); - - try { - const existingWatch = await fetchWatch(callWithRequest, id); - - if (existingWatch.found) { - throw conflictError; - } - } catch (e) { - // Rethrow conflict error but silently swallow all others - if (e === conflictError) { - throw e; - } - } - } - - let serializedWatch; - - switch (type) { - case WATCH_TYPES.JSON: - const { name, watch } = watchConfig; - serializedWatch = serializeJsonWatch(name, watch); - break; - - case WATCH_TYPES.THRESHOLD: - serializedWatch = serializeThresholdWatch(watchConfig); - break; - } - - // Create new watch - return saveWatch(callWithRequest, id, serializedWatch) - .catch(err => { - // Case: Error from Elasticsearch JS client - if (isEsError(err)) { - throw wrapEsError(err); - } - - // Case: default - throw wrapUnknownError(err); - }); - }, - config: { - pre: [ licensePreRouting ] - } - }); -} diff --git a/x-pack/legacy/plugins/watcher/server/routes/api/watch/register_visualize_route.js b/x-pack/legacy/plugins/watcher/server/routes/api/watch/register_visualize_route.js deleted file mode 100644 index ff9d8f9775d5..000000000000 --- a/x-pack/legacy/plugins/watcher/server/routes/api/watch/register_visualize_route.js +++ /dev/null @@ -1,62 +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 { callWithRequestFactory } from '../../../lib/call_with_request_factory'; -import { Watch } from '../../../models/watch'; -import { VisualizeOptions } from '../../../models/visualize_options'; -import { isEsErrorFactory } from '../../../lib/is_es_error_factory'; -import { wrapEsError, wrapUnknownError } from '../../../lib/error_wrappers'; -import { licensePreRoutingFactory } from'../../../lib/license_pre_routing_factory'; - -function fetchVisualizeData(callWithRequest, index, body) { - const params = { - index, - body, - ignoreUnavailable: true, - allowNoIndices: true, - ignore: [404] - }; - - return callWithRequest('search', params); -} - -export function registerVisualizeRoute(server) { - const isEsError = isEsErrorFactory(server); - const licensePreRouting = licensePreRoutingFactory(server); - - server.route({ - path: '/api/watcher/watch/visualize', - method: 'POST', - handler: (request) => { - const callWithRequest = callWithRequestFactory(server, request); - const watch = Watch.fromDownstreamJson(request.payload.watch); - const options = VisualizeOptions.fromDownstreamJson(request.payload.options); - const body = watch.getVisualizeQuery(options); - - return fetchVisualizeData(callWithRequest, watch.index, body) - .then(hits => { - const visualizeData = watch.formatVisualizeData(hits); - - return { - visualizeData - }; - }) - .catch(err => { - - // Case: Error from Elasticsearch JS client - if (isEsError(err)) { - throw wrapEsError(err); - } - - // Case: default - throw wrapUnknownError(err); - }); - }, - config: { - pre: [ licensePreRouting ] - } - }); -} diff --git a/x-pack/legacy/plugins/watcher/server/routes/api/watches/register_delete_route.js b/x-pack/legacy/plugins/watcher/server/routes/api/watches/register_delete_route.js deleted file mode 100644 index a0bbfb954b75..000000000000 --- a/x-pack/legacy/plugins/watcher/server/routes/api/watches/register_delete_route.js +++ /dev/null @@ -1,58 +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 { callWithRequestFactory } from '../../../lib/call_with_request_factory'; -import { wrapUnknownError } from '../../../lib/error_wrappers'; -import { licensePreRoutingFactory } from '../../../lib/license_pre_routing_factory'; - -function deleteWatches(callWithRequest, watchIds) { - const deletePromises = watchIds.map(watchId => { - return callWithRequest('watcher.deleteWatch', { - id: watchId, - }) - .then(success => ({ success })) - .catch(error => ({ error })); - }); - - return Promise.all(deletePromises).then(results => { - const errors = []; - const successes = []; - results.forEach(({ success, error }) => { - if (success) { - successes.push(success._id); - } else if (error) { - errors.push(error._id); - } - }); - - return { - successes, - errors, - }; - }); -} - -export function registerDeleteRoute(server) { - const licensePreRouting = licensePreRoutingFactory(server); - - server.route({ - path: '/api/watcher/watches/delete', - method: 'POST', - handler: async request => { - const callWithRequest = callWithRequestFactory(server, request); - - try { - const results = await deleteWatches(callWithRequest, request.payload.watchIds); - return { results }; - } catch (err) { - throw wrapUnknownError(err); - } - }, - config: { - pre: [licensePreRouting], - }, - }); -} diff --git a/x-pack/legacy/plugins/watcher/server/routes/api/watches/register_list_route.js b/x-pack/legacy/plugins/watcher/server/routes/api/watches/register_list_route.js deleted file mode 100644 index 2a617e275d1e..000000000000 --- a/x-pack/legacy/plugins/watcher/server/routes/api/watches/register_list_route.js +++ /dev/null @@ -1,79 +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 { get } from 'lodash'; -import { callWithRequestFactory } from '../../../lib/call_with_request_factory'; -import { fetchAllFromScroll } from '../../../lib/fetch_all_from_scroll'; -import { INDEX_NAMES, ES_SCROLL_SETTINGS } from '../../../../common/constants'; -import { Watch } from '../../../models/watch'; -import { isEsErrorFactory } from '../../../lib/is_es_error_factory'; -import { wrapEsError, wrapUnknownError } from '../../../lib/error_wrappers'; -import { licensePreRoutingFactory } from'../../../lib/license_pre_routing_factory'; - -function fetchWatches(callWithRequest) { - const params = { - index: INDEX_NAMES.WATCHES, - scroll: ES_SCROLL_SETTINGS.KEEPALIVE, - body: { - size: ES_SCROLL_SETTINGS.PAGE_SIZE, - }, - ignore: [404] - }; - - return callWithRequest('search', params) - .then(response => fetchAllFromScroll(response, callWithRequest)); -} - -export function registerListRoute(server) { - const isEsError = isEsErrorFactory(server); - const licensePreRouting = licensePreRoutingFactory(server); - - server.route({ - path: '/api/watcher/watches', - method: 'GET', - handler: (request) => { - const callWithRequest = callWithRequestFactory(server, request); - - return fetchWatches(callWithRequest) - .then(hits => { - const watches = hits.map(hit => { - const id = get(hit, '_id'); - const watchJson = get(hit, '_source'); - const watchStatusJson = get(hit, '_source.status'); - - return Watch.fromUpstreamJson( - { - id, - watchJson, - watchStatusJson, - }, - { - throwExceptions: { - Action: false, - }, - } - ); - }); - - return { - watches: watches.map(watch => watch.downstreamJson) - }; - }) - .catch(err => { - // Case: Error from Elasticsearch JS client - if (isEsError(err)) { - throw wrapEsError(err); - } - - // Case: default - throw wrapUnknownError(err); - }); - }, - config: { - pre: [ licensePreRouting ] - } - }); -} diff --git a/x-pack/plugins/advanced_ui_actions/public/does_inherit_time_range.ts b/x-pack/plugins/advanced_ui_actions/public/does_inherit_time_range.ts index 4cfe581b7eac..d1568a5ab96c 100644 --- a/x-pack/plugins/advanced_ui_actions/public/does_inherit_time_range.ts +++ b/x-pack/plugins/advanced_ui_actions/public/does_inherit_time_range.ts @@ -17,6 +17,10 @@ export function doesInheritTimeRange(embeddable: Embeddable) { // Note: this logic might not work in a container nested world... the explicit input // may be on the root... or any of the interim parents. + // if it's a dashboard emptys screen, there will be no embeddable + if (!parent.getInput().panels[embeddable.id]) { + return false; + } // If there is no explicit input defined on the parent then this embeddable inherits the // time range from whatever the time range of the parent is. return parent.getInput().panels[embeddable.id].explicitInput.timeRange === undefined; diff --git a/x-pack/plugins/security/server/authorization/check_privileges.test.ts b/x-pack/plugins/security/server/authorization/check_privileges.test.ts index b1cb78008da0..8c1241937892 100644 --- a/x-pack/plugins/security/server/authorization/check_privileges.test.ts +++ b/x-pack/plugins/security/server/authorization/check_privileges.test.ts @@ -48,7 +48,7 @@ describe('#atSpace', () => { const checkPrivilegesWithRequest = checkPrivilegesWithRequestFactory( mockActions, mockClusterClient, - () => application + application ); const request = httpServerMock.createKibanaRequest(); const checkPrivileges = checkPrivilegesWithRequest(request); @@ -291,7 +291,7 @@ describe('#atSpaces', () => { const checkPrivilegesWithRequest = checkPrivilegesWithRequestFactory( mockActions, mockClusterClient, - () => application + application ); const request = httpServerMock.createKibanaRequest(); const checkPrivileges = checkPrivilegesWithRequest(request); @@ -772,7 +772,7 @@ describe('#globally', () => { const checkPrivilegesWithRequest = checkPrivilegesWithRequestFactory( mockActions, mockClusterClient, - () => application + application ); const request = httpServerMock.createKibanaRequest(); const checkPrivileges = checkPrivilegesWithRequest(request); diff --git a/x-pack/plugins/security/server/authorization/check_privileges.ts b/x-pack/plugins/security/server/authorization/check_privileges.ts index 5bc3ce075452..3ef7a8f29a0b 100644 --- a/x-pack/plugins/security/server/authorization/check_privileges.ts +++ b/x-pack/plugins/security/server/authorization/check_privileges.ts @@ -61,7 +61,7 @@ export interface CheckPrivileges { export function checkPrivilegesWithRequestFactory( actions: CheckPrivilegesActions, clusterClient: IClusterClient, - getApplicationName: () => string + applicationName: string ) { const hasIncompatibleVersion = ( applicationPrivilegesResponse: HasPrivilegesResponseApplication @@ -81,23 +81,24 @@ export function checkPrivilegesWithRequestFactory( : [privilegeOrPrivileges]; const allApplicationPrivileges = uniq([actions.version, actions.login, ...privileges]); - const application = getApplicationName(); const hasPrivilegesResponse = (await clusterClient .asScoped(request) .callAsCurrentUser('shield.hasPrivileges', { body: { - applications: [{ application, resources, privileges: allApplicationPrivileges }], + applications: [ + { application: applicationName, resources, privileges: allApplicationPrivileges }, + ], }, })) as HasPrivilegesResponse; validateEsPrivilegeResponse( hasPrivilegesResponse, - application, + applicationName, allApplicationPrivileges, resources ); - const applicationPrivilegesResponse = hasPrivilegesResponse.application[application]; + const applicationPrivilegesResponse = hasPrivilegesResponse.application[applicationName]; if (hasIncompatibleVersion(applicationPrivilegesResponse)) { throw new Error( diff --git a/x-pack/plugins/security/server/authorization/index.mock.ts b/x-pack/plugins/security/server/authorization/index.mock.ts index 2e700745c69d..930ede415772 100644 --- a/x-pack/plugins/security/server/authorization/index.mock.ts +++ b/x-pack/plugins/security/server/authorization/index.mock.ts @@ -8,12 +8,15 @@ import { Actions } from '.'; import { AuthorizationMode } from './mode'; export const authorizationMock = { - create: ({ version = 'mock-version' }: { version?: string } = {}) => ({ + create: ({ + version = 'mock-version', + applicationName = 'mock-application', + }: { version?: string; applicationName?: string } = {}) => ({ actions: new Actions(version), checkPrivilegesWithRequest: jest.fn(), checkPrivilegesDynamicallyWithRequest: jest.fn(), checkSavedObjectsPrivilegesWithRequest: jest.fn(), - getApplicationName: jest.fn().mockReturnValue('mock-application'), + applicationName, mode: { useRbacForRequest: jest.fn() } as jest.Mocked, privileges: { get: jest.fn() }, registerPrivilegesWithCluster: jest.fn(), diff --git a/x-pack/plugins/security/server/authorization/index.test.ts b/x-pack/plugins/security/server/authorization/index.test.ts index 24179e062230..34b9efea7716 100644 --- a/x-pack/plugins/security/server/authorization/index.test.ts +++ b/x-pack/plugins/security/server/authorization/index.test.ts @@ -53,7 +53,6 @@ test(`returns exposed services`, () => { .fn() .mockReturnValue({ getSpaceId: jest.fn(), namespaceToSpaceId: jest.fn() }); const mockFeaturesService = { getFeatures: () => [] }; - const mockGetLegacyAPI = () => ({ kibanaIndexName }); const mockLicense = licenseMock.create(); const authz = setupAuthorization({ @@ -61,20 +60,20 @@ test(`returns exposed services`, () => { clusterClient: mockClusterClient, license: mockLicense, loggers: loggingServiceMock.create(), - getLegacyAPI: mockGetLegacyAPI, + kibanaIndexName, packageVersion: 'some-version', featuresService: mockFeaturesService, getSpacesService: mockGetSpacesService, }); expect(authz.actions.version).toBe('version:some-version'); - expect(authz.getApplicationName()).toBe(application); + expect(authz.applicationName).toBe(application); expect(authz.checkPrivilegesWithRequest).toBe(mockCheckPrivilegesWithRequest); expect(checkPrivilegesWithRequestFactory).toHaveBeenCalledWith( authz.actions, mockClusterClient, - authz.getApplicationName + authz.applicationName ); expect(authz.checkPrivilegesDynamicallyWithRequest).toBe( diff --git a/x-pack/plugins/security/server/authorization/index.ts b/x-pack/plugins/security/server/authorization/index.ts index b5f9efadbd8d..41e6d12eb8f3 100644 --- a/x-pack/plugins/security/server/authorization/index.ts +++ b/x-pack/plugins/security/server/authorization/index.ts @@ -12,7 +12,7 @@ import { IClusterClient, } from '../../../../../src/core/server'; -import { FeaturesService, LegacyAPI, SpacesService } from '../plugin'; +import { FeaturesService, SpacesService } from '../plugin'; import { Actions } from './actions'; import { CheckPrivilegesWithRequest, checkPrivilegesWithRequestFactory } from './check_privileges'; import { @@ -43,7 +43,7 @@ interface SetupAuthorizationParams { license: SecurityLicense; loggers: LoggerFactory; featuresService: FeaturesService; - getLegacyAPI(): Pick; + kibanaIndexName: string; getSpacesService(): SpacesService | undefined; } @@ -52,7 +52,7 @@ export interface Authorization { checkPrivilegesWithRequest: CheckPrivilegesWithRequest; checkPrivilegesDynamicallyWithRequest: CheckPrivilegesDynamicallyWithRequest; checkSavedObjectsPrivilegesWithRequest: CheckSavedObjectsPrivilegesWithRequest; - getApplicationName: () => string; + applicationName: string; mode: AuthorizationMode; privileges: PrivilegesService; disableUnauthorizedCapabilities: ( @@ -69,23 +69,23 @@ export function setupAuthorization({ license, loggers, featuresService, - getLegacyAPI, + kibanaIndexName, getSpacesService, }: SetupAuthorizationParams): Authorization { const actions = new Actions(packageVersion); const mode = authorizationModeFactory(license); - const getApplicationName = () => `${APPLICATION_PREFIX}${getLegacyAPI().kibanaIndexName}`; + const applicationName = `${APPLICATION_PREFIX}${kibanaIndexName}`; const checkPrivilegesWithRequest = checkPrivilegesWithRequestFactory( actions, clusterClient, - getApplicationName + applicationName ); const privileges = privilegesFactory(actions, featuresService); const logger = loggers.get('authorization'); const authz = { actions, - getApplicationName, + applicationName, checkPrivilegesWithRequest, checkPrivilegesDynamicallyWithRequest: checkPrivilegesDynamicallyWithRequestFactory( checkPrivilegesWithRequest, @@ -123,7 +123,7 @@ export function setupAuthorization({ registerPrivilegesWithCluster: async () => { validateFeaturePrivileges(actions, featuresService.getFeatures()); - await registerPrivilegesWithCluster(logger, privileges, getApplicationName(), clusterClient); + await registerPrivilegesWithCluster(logger, privileges, applicationName, clusterClient); }, }; diff --git a/x-pack/plugins/security/server/plugin.ts b/x-pack/plugins/security/server/plugin.ts index cb197ecaf7e1..84d448331cef 100644 --- a/x-pack/plugins/security/server/plugin.ts +++ b/x-pack/plugins/security/server/plugin.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Subscription } from 'rxjs'; +import { Subscription, combineLatest } from 'rxjs'; import { first } from 'rxjs/operators'; import { IClusterClient, @@ -42,7 +42,6 @@ export type FeaturesService = Pick; */ export interface LegacyAPI { isSystemAPIRequest: (request: KibanaRequest) => boolean; - kibanaIndexName: string; cspRules: string; savedObjects: SavedObjectsLegacyService; auditLogger: { @@ -121,7 +120,10 @@ export class Plugin { core: CoreSetup, { features, licensing }: PluginSetupDependencies ): Promise> { - const config = await createConfig$(this.initializerContext, core.http.isTlsEnabled) + const [config, legacyConfig] = await combineLatest([ + createConfig$(this.initializerContext, core.http.isTlsEnabled), + this.initializerContext.config.legacy.globalConfig$, + ]) .pipe(first()) .toPromise(); @@ -148,7 +150,7 @@ export class Plugin { clusterClient: this.clusterClient, license, loggers: this.initializerContext.logger, - getLegacyAPI: this.getLegacyAPI, + kibanaIndexName: legacyConfig.kibana.index, packageVersion: this.initializerContext.env.packageInfo.version, getSpacesService: this.getSpacesService, featuresService: features, diff --git a/x-pack/plugins/security/server/routes/authorization/roles/get.test.ts b/x-pack/plugins/security/server/routes/authorization/roles/get.test.ts index 1cfc1ae416ae..447d890605cc 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/get.test.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/get.test.ts @@ -36,7 +36,7 @@ describe('GET role', () => { ) => { test(description, async () => { const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); - mockRouteDefinitionParams.authz.getApplicationName.mockReturnValue(application); + mockRouteDefinitionParams.authz.applicationName = application; const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); mockRouteDefinitionParams.clusterClient.asScoped.mockReturnValue(mockScopedClusterClient); diff --git a/x-pack/plugins/security/server/routes/authorization/roles/get.ts b/x-pack/plugins/security/server/routes/authorization/roles/get.ts index be69e222dd09..1173d37cba64 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/get.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/get.ts @@ -28,7 +28,7 @@ export function defineGetRolesRoutes({ router, authz, clusterClient }: RouteDefi body: transformElasticsearchRoleToRole( elasticsearchRole, request.params.name, - authz.getApplicationName() + authz.applicationName ), }); } diff --git a/x-pack/plugins/security/server/routes/authorization/roles/get_all.test.ts b/x-pack/plugins/security/server/routes/authorization/roles/get_all.test.ts index 76ce6a272e28..3bd85122c95d 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/get_all.test.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/get_all.test.ts @@ -31,7 +31,7 @@ describe('GET all roles', () => { ) => { test(description, async () => { const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); - mockRouteDefinitionParams.authz.getApplicationName.mockReturnValue(application); + mockRouteDefinitionParams.authz.applicationName = application; const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); mockRouteDefinitionParams.clusterClient.asScoped.mockReturnValue(mockScopedClusterClient); diff --git a/x-pack/plugins/security/server/routes/authorization/roles/get_all.ts b/x-pack/plugins/security/server/routes/authorization/roles/get_all.ts index f5d2d51280fc..6ff431f0f8b6 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/get_all.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/get_all.ts @@ -22,11 +22,7 @@ export function defineGetAllRolesRoutes({ router, authz, clusterClient }: RouteD return response.ok({ body: Object.entries(elasticsearchRoles) .map(([roleName, elasticsearchRole]) => - transformElasticsearchRoleToRole( - elasticsearchRole, - roleName, - authz.getApplicationName() - ) + transformElasticsearchRoleToRole(elasticsearchRole, roleName, authz.applicationName) ) .sort((roleA, roleB) => { if (roleA.name < roleB.name) { diff --git a/x-pack/plugins/security/server/routes/authorization/roles/put.test.ts b/x-pack/plugins/security/server/routes/authorization/roles/put.test.ts index 31963987c2ef..cb80549df841 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/put.test.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/put.test.ts @@ -62,7 +62,7 @@ const putRoleTest = ( ) => { test(description, async () => { const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); - mockRouteDefinitionParams.authz.getApplicationName.mockReturnValue(application); + mockRouteDefinitionParams.authz.applicationName = application; mockRouteDefinitionParams.authz.privileges.get.mockReturnValue(privilegeMap); const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); diff --git a/x-pack/plugins/security/server/routes/authorization/roles/put.ts b/x-pack/plugins/security/server/routes/authorization/roles/put.ts index 92c940132e66..e0245e726044 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/put.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/put.ts @@ -42,7 +42,7 @@ export function definePutRolesRoutes({ router, authz, clusterClient }: RouteDefi const body = transformPutPayloadToElasticsearchRole( request.body, - authz.getApplicationName(), + authz.applicationName, rawRoles[name] ? rawRoles[name].applications : [] ); diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index d51332c65aa5..55147c7863d1 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -12736,4 +12736,4 @@ "xpack.licensing.welcomeBanner.licenseIsExpiredDescription.updateYourLicenseLinkText": "ライセンスを更新", "xpack.licensing.welcomeBanner.licenseIsExpiredTitle": "ご使用の {licenseType} ライセンスは期限切れです" } -} +} \ No newline at end of file diff --git a/x-pack/test/api_integration/apis/index.js b/x-pack/test/api_integration/apis/index.js index ed0e6488320d..fd700b41df56 100644 --- a/x-pack/test/api_integration/apis/index.js +++ b/x-pack/test/api_integration/apis/index.js @@ -28,5 +28,6 @@ export default function ({ loadTestFile }) { loadTestFile(require.resolve('./short_urls')); loadTestFile(require.resolve('./lens')); loadTestFile(require.resolve('./endpoint')); + loadTestFile(require.resolve('./ml')); }); } diff --git a/x-pack/test/api_integration/apis/ml/bucket_span_estimator.ts b/x-pack/test/api_integration/apis/ml/bucket_span_estimator.ts new file mode 100644 index 000000000000..b5e516862158 --- /dev/null +++ b/x-pack/test/api_integration/apis/ml/bucket_span_estimator.ts @@ -0,0 +1,90 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; + +import { FtrProviderContext } from '../../ftr_provider_context'; + +const COMMON_HEADERS = { + 'kbn-xsrf': 'some-xsrf-token', +}; + +const testDataList = [ + { + testTitleSuffix: 'with 1 field, 1 agg, no split', + requestBody: { + aggTypes: ['avg'], + duration: { start: 1560297859000, end: 1562975136000 }, + fields: ['taxless_total_price'], + index: 'ecommerce', + query: { bool: { must: [{ match_all: {} }] } }, + timeField: 'order_date', + }, + expected: { + responseCode: 200, + responseBody: { name: '15m', ms: 900000 }, + }, + }, + { + testTitleSuffix: 'with 2 fields, 2 aggs, no split', + requestBody: { + aggTypes: ['avg', 'sum'], + duration: { start: 1560297859000, end: 1562975136000 }, + fields: ['products.base_price', 'products.base_unit_price'], + index: 'ecommerce', + query: { bool: { must: [{ match_all: {} }] } }, + timeField: 'order_date', + }, + expected: { + responseCode: 200, + responseBody: { name: '30m', ms: 1800000 }, + }, + }, + { + testTitleSuffix: 'with 1 field, 1 agg, 1 split with cardinality 46', + requestBody: { + aggTypes: ['avg'], + duration: { start: 1560297859000, end: 1562975136000 }, + fields: ['taxless_total_price'], + index: 'ecommerce', + query: { bool: { must: [{ match_all: {} }] } }, + splitField: 'customer_first_name.keyword', + timeField: 'order_date', + }, + expected: { + responseCode: 200, + responseBody: { name: '3h', ms: 10800000 }, + }, + }, +]; + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext) => { + const esArchiver = getService('esArchiver'); + const supertest = getService('supertest'); + + describe('bucket span estimator', () => { + before(async () => { + await esArchiver.load('ml/ecommerce'); + }); + + after(async () => { + await esArchiver.unload('ml/ecommerce'); + }); + + for (const testData of testDataList) { + it(`estimates the bucket span ${testData.testTitleSuffix}`, async () => { + const { body } = await supertest + .post('/api/ml/validate/estimate_bucket_span') + .set(COMMON_HEADERS) + .send(testData.requestBody) + .expect(testData.expected.responseCode); + + expect(body).to.eql(testData.expected.responseBody); + }); + } + }); +}; diff --git a/x-pack/test/api_integration/apis/ml/index.ts b/x-pack/test/api_integration/apis/ml/index.ts new file mode 100644 index 000000000000..2e0521e2b827 --- /dev/null +++ b/x-pack/test/api_integration/apis/ml/index.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function({ loadTestFile }: FtrProviderContext) { + describe('Machine Learning', function() { + this.tags(['mlqa']); + + loadTestFile(require.resolve('./bucket_span_estimator')); + }); +} diff --git a/x-pack/test/api_integration/apis/short_urls/feature_controls.ts b/x-pack/test/api_integration/apis/short_urls/feature_controls.ts index 06fd971399ea..db5e11ef367a 100644 --- a/x-pack/test/api_integration/apis/short_urls/feature_controls.ts +++ b/x-pack/test/api_integration/apis/short_urls/feature_controls.ts @@ -107,7 +107,7 @@ export default function featureControlsTests({ getService }: FtrProviderContext) expect(resp.status).to.eql(302); expect(resp.headers.location).to.eql('/app/kibana#foo/bar/baz'); } else { - expect(resp.status).to.eql(500); + expect(resp.status).to.eql(403); expect(resp.headers.location).to.eql(undefined); } });