From 265cc7624768fef99a7cad9045a79e8610308886 Mon Sep 17 00:00:00 2001 From: Pierre Gayvallet Date: Tue, 30 Mar 2021 13:39:32 +0200 Subject: [PATCH 01/32] Remove core->cli dependency (#95145) * extract http_tools to package * fix readme * start moving stuff * cleaning up `isDevCliParent` * choose bootstrap script * fix bootstrap script logic * fix watch paths logic * import REPO_ROOT from correct package * create the @kbn/crypto package * update core's `dev` config * only export bootstrap function * extract sslConfig to http-tools package * fix core types * fix optimizer tests * fix cli_dev_mode tests * fix basePath proxy tests * update generated doc * fix unit tests * create @kbn/dev-cli-mode package * remove useless comment * self-review NITS * update CODEOWNERS file * add devOnly flag * use variable for DEV_MODE_PATH * review comments * fix logger/log adapter * fix log calls in base path proxy server * address some review comments * rename @kbn/http-tools to @kbn/server-http-tools * more review comments * move test to correct file * add comment on getBootstrapScript * fix lint * lint * add cli-dev-mode to eslint dev packages * review comments * update yarn.lock * Revert "[ci] skip building ts refs when not necessary (#95739)" This reverts commit e46a74f7 --- .eslintrc.js | 1 + .github/CODEOWNERS | 3 + ...lugin-core-server.kibanaresponsefactory.md | 4 +- package.json | 3 + .../kbn-cli-dev-mode}/README.md | 10 +- packages/kbn-cli-dev-mode/jest.config.js | 13 + packages/kbn-cli-dev-mode/package.json | 26 + .../src/base_path_proxy_server.test.ts | 358 ++++++ .../src}/base_path_proxy_server.ts | 46 +- packages/kbn-cli-dev-mode/src/bootstrap.ts | 43 + .../src}/cli_dev_mode.test.ts | 139 +-- .../kbn-cli-dev-mode/src}/cli_dev_mode.ts | 84 +- .../kbn-cli-dev-mode/src/config/dev_config.ts | 28 + .../src/config/http_config.ts | 65 ++ packages/kbn-cli-dev-mode/src/config/index.ts | 13 + .../src/config/load_config.ts | 44 + .../src/config/plugins_config.ts | 37 + packages/kbn-cli-dev-mode/src/config/types.ts | 17 + .../kbn-cli-dev-mode/src}/dev_server.test.ts | 0 .../kbn-cli-dev-mode/src}/dev_server.ts | 0 .../src}/get_active_inspect_flag.ts | 0 .../src}/get_server_watch_paths.test.ts | 0 .../src}/get_server_watch_paths.ts | 10 +- .../kbn-cli-dev-mode/src}/index.ts | 3 +- .../kbn-cli-dev-mode/src}/log.ts | 0 packages/kbn-cli-dev-mode/src/log_adapter.ts | 28 + .../kbn-cli-dev-mode/src}/optimizer.test.ts | 6 + .../kbn-cli-dev-mode/src}/optimizer.ts | 2 + ...should_redirect_from_old_base_path.test.ts | 0 .../should_redirect_from_old_base_path.ts | 0 .../kbn-cli-dev-mode/src}/test_helpers.ts | 0 .../src}/using_server_process.ts | 0 .../kbn-cli-dev-mode/src}/watcher.test.ts | 0 .../kbn-cli-dev-mode/src}/watcher.ts | 0 packages/kbn-cli-dev-mode/tsconfig.json | 11 + packages/kbn-config/src/__mocks__/env.ts | 1 - .../src/__snapshots__/env.test.ts.snap | 6 - packages/kbn-config/src/env.test.ts | 1 - packages/kbn-config/src/env.ts | 8 - packages/kbn-config/src/index.ts | 7 +- packages/kbn-config/src/raw/index.ts | 2 +- .../kbn-config/src/raw/raw_config_service.ts | 2 +- packages/kbn-crypto/README.md | 3 + packages/kbn-crypto/jest.config.js | 13 + packages/kbn-crypto/package.json | 16 + .../kbn-crypto/src}/__fixtures__/README.md | 0 .../kbn-crypto/src}/__fixtures__/index.ts | 0 .../kbn-crypto/src}/__fixtures__/no_ca.p12 | Bin .../kbn-crypto/src}/__fixtures__/no_cert.p12 | Bin .../kbn-crypto/src}/__fixtures__/no_key.p12 | Bin .../kbn-crypto/src}/__fixtures__/two_cas.p12 | Bin .../kbn-crypto/src}/__fixtures__/two_keys.p12 | Bin .../kbn-crypto/src}/index.ts | 0 .../kbn-crypto/src}/pkcs12.test.ts | 2 +- .../kbn-crypto/src}/pkcs12.ts | 0 .../kbn-crypto/src}/sha256.test.ts | 0 .../kbn-crypto/src}/sha256.ts | 0 packages/kbn-crypto/tsconfig.json | 11 + packages/kbn-server-http-tools/README.md | 3 + packages/kbn-server-http-tools/jest.config.js | 13 + packages/kbn-server-http-tools/package.json | 20 + .../src/create_server.ts | 29 + .../default_validation_error_handler.test.ts | 51 + .../src/default_validation_error_handler.ts | 63 + .../src/get_listener_options.ts | 21 + .../src/get_request_id.test.ts | 85 ++ .../src/get_request_id.ts | 22 + .../src/get_server_options.test.ts | 122 ++ .../src/get_server_options.ts | 75 ++ packages/kbn-server-http-tools/src/index.ts | 15 + .../kbn-server-http-tools/src/ssl/index.ts | 2 +- .../src/ssl}/ssl_config.test.mocks.ts | 2 +- .../src/ssl}/ssl_config.test.ts | 2 +- .../src/ssl}/ssl_config.ts | 17 +- packages/kbn-server-http-tools/src/types.ts | 37 + packages/kbn-server-http-tools/tsconfig.json | 14 + packages/kbn-utils/src/repo_root.ts | 2 + src/cli/serve/serve.js | 78 +- src/core/server/bootstrap.ts | 16 +- src/core/server/dev/dev_config.ts | 23 +- src/core/server/dev/index.ts | 3 +- .../elasticsearch_config.test.mocks.ts | 2 +- .../elasticsearch_config.test.ts | 6 +- .../elasticsearch/elasticsearch_config.ts | 2 +- .../external_url/external_url_config.ts | 2 +- .../http/base_path_proxy_server.test.ts | 1021 ----------------- src/core/server/http/http_config.ts | 4 +- src/core/server/http/http_server.test.ts | 24 + src/core/server/http/http_server.ts | 7 +- src/core/server/http/http_service.test.ts | 23 - src/core/server/http/http_service.ts | 10 +- src/core/server/http/http_tools.test.ts | 274 ----- src/core/server/http/http_tools.ts | 186 --- src/core/server/http/https_redirect_server.ts | 2 +- src/core/server/http/index.ts | 1 - .../http/integration_tests/router.test.ts | 5 +- src/core/server/legacy/legacy_service.test.ts | 73 -- src/core/server/legacy/legacy_service.ts | 45 +- .../server/plugins/plugins_service.test.ts | 34 +- src/core/server/plugins/plugins_service.ts | 20 +- src/core/server/root/index.ts | 9 +- src/core/server/server.api.md | 8 +- src/core/server/server.test.ts | 16 - src/core/server/server.ts | 11 +- src/core/server/utils/index.ts | 1 - src/core/test_helpers/kbn_server.ts | 1 - yarn.lock | 12 + 107 files changed, 1611 insertions(+), 1969 deletions(-) rename {src/dev/cli_dev_mode => packages/kbn-cli-dev-mode}/README.md (72%) create mode 100644 packages/kbn-cli-dev-mode/jest.config.js create mode 100644 packages/kbn-cli-dev-mode/package.json create mode 100644 packages/kbn-cli-dev-mode/src/base_path_proxy_server.test.ts rename {src/core/server/http => packages/kbn-cli-dev-mode/src}/base_path_proxy_server.ts (90%) create mode 100644 packages/kbn-cli-dev-mode/src/bootstrap.ts rename {src/dev/cli_dev_mode => packages/kbn-cli-dev-mode/src}/cli_dev_mode.test.ts (79%) rename {src/dev/cli_dev_mode => packages/kbn-cli-dev-mode/src}/cli_dev_mode.ts (83%) create mode 100644 packages/kbn-cli-dev-mode/src/config/dev_config.ts create mode 100644 packages/kbn-cli-dev-mode/src/config/http_config.ts create mode 100644 packages/kbn-cli-dev-mode/src/config/index.ts create mode 100644 packages/kbn-cli-dev-mode/src/config/load_config.ts create mode 100644 packages/kbn-cli-dev-mode/src/config/plugins_config.ts create mode 100644 packages/kbn-cli-dev-mode/src/config/types.ts rename {src/dev/cli_dev_mode => packages/kbn-cli-dev-mode/src}/dev_server.test.ts (100%) rename {src/dev/cli_dev_mode => packages/kbn-cli-dev-mode/src}/dev_server.ts (100%) rename {src/dev/cli_dev_mode => packages/kbn-cli-dev-mode/src}/get_active_inspect_flag.ts (100%) rename {src/dev/cli_dev_mode => packages/kbn-cli-dev-mode/src}/get_server_watch_paths.test.ts (100%) rename {src/dev/cli_dev_mode => packages/kbn-cli-dev-mode/src}/get_server_watch_paths.ts (87%) rename {src/dev/cli_dev_mode => packages/kbn-cli-dev-mode/src}/index.ts (86%) rename {src/dev/cli_dev_mode => packages/kbn-cli-dev-mode/src}/log.ts (100%) create mode 100644 packages/kbn-cli-dev-mode/src/log_adapter.ts rename {src/dev/cli_dev_mode => packages/kbn-cli-dev-mode/src}/optimizer.test.ts (96%) rename {src/dev/cli_dev_mode => packages/kbn-cli-dev-mode/src}/optimizer.ts (97%) rename {src/dev/cli_dev_mode => packages/kbn-cli-dev-mode/src}/should_redirect_from_old_base_path.test.ts (100%) rename {src/dev/cli_dev_mode => packages/kbn-cli-dev-mode/src}/should_redirect_from_old_base_path.ts (100%) rename {src/dev/cli_dev_mode => packages/kbn-cli-dev-mode/src}/test_helpers.ts (100%) rename {src/dev/cli_dev_mode => packages/kbn-cli-dev-mode/src}/using_server_process.ts (100%) rename {src/dev/cli_dev_mode => packages/kbn-cli-dev-mode/src}/watcher.test.ts (100%) rename {src/dev/cli_dev_mode => packages/kbn-cli-dev-mode/src}/watcher.ts (100%) create mode 100644 packages/kbn-cli-dev-mode/tsconfig.json create mode 100644 packages/kbn-crypto/README.md create mode 100644 packages/kbn-crypto/jest.config.js create mode 100644 packages/kbn-crypto/package.json rename {src/core/server/utils/crypto => packages/kbn-crypto/src}/__fixtures__/README.md (100%) rename {src/core/server/utils/crypto => packages/kbn-crypto/src}/__fixtures__/index.ts (100%) rename {src/core/server/utils/crypto => packages/kbn-crypto/src}/__fixtures__/no_ca.p12 (100%) rename {src/core/server/utils/crypto => packages/kbn-crypto/src}/__fixtures__/no_cert.p12 (100%) rename {src/core/server/utils/crypto => packages/kbn-crypto/src}/__fixtures__/no_key.p12 (100%) rename {src/core/server/utils/crypto => packages/kbn-crypto/src}/__fixtures__/two_cas.p12 (100%) rename {src/core/server/utils/crypto => packages/kbn-crypto/src}/__fixtures__/two_keys.p12 (100%) rename {src/core/server/utils/crypto => packages/kbn-crypto/src}/index.ts (100%) rename {src/core/server/utils/crypto => packages/kbn-crypto/src}/pkcs12.test.ts (99%) rename {src/core/server/utils/crypto => packages/kbn-crypto/src}/pkcs12.ts (100%) rename {src/core/server/utils/crypto => packages/kbn-crypto/src}/sha256.test.ts (100%) rename {src/core/server/utils/crypto => packages/kbn-crypto/src}/sha256.ts (100%) create mode 100644 packages/kbn-crypto/tsconfig.json create mode 100644 packages/kbn-server-http-tools/README.md create mode 100644 packages/kbn-server-http-tools/jest.config.js create mode 100644 packages/kbn-server-http-tools/package.json create mode 100644 packages/kbn-server-http-tools/src/create_server.ts create mode 100644 packages/kbn-server-http-tools/src/default_validation_error_handler.test.ts create mode 100644 packages/kbn-server-http-tools/src/default_validation_error_handler.ts create mode 100644 packages/kbn-server-http-tools/src/get_listener_options.ts create mode 100644 packages/kbn-server-http-tools/src/get_request_id.test.ts create mode 100644 packages/kbn-server-http-tools/src/get_request_id.ts create mode 100644 packages/kbn-server-http-tools/src/get_server_options.test.ts create mode 100644 packages/kbn-server-http-tools/src/get_server_options.ts create mode 100644 packages/kbn-server-http-tools/src/index.ts rename src/core/server/legacy/cli_dev_mode.js => packages/kbn-server-http-tools/src/ssl/index.ts (86%) rename {src/core/server/http => packages/kbn-server-http-tools/src/ssl}/ssl_config.test.mocks.ts (95%) rename {src/core/server/http => packages/kbn-server-http-tools/src/ssl}/ssl_config.test.ts (99%) rename {src/core/server/http => packages/kbn-server-http-tools/src/ssl}/ssl_config.ts (93%) create mode 100644 packages/kbn-server-http-tools/src/types.ts create mode 100644 packages/kbn-server-http-tools/tsconfig.json delete mode 100644 src/core/server/http/base_path_proxy_server.test.ts delete mode 100644 src/core/server/http/http_tools.test.ts delete mode 100644 src/core/server/http/http_tools.ts diff --git a/.eslintrc.js b/.eslintrc.js index ab868c29b7bed8..a7b45534391c0a 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -93,6 +93,7 @@ const SAFER_LODASH_SET_DEFINITELYTYPED_HEADER = ` const DEV_PACKAGES = [ 'kbn-babel-code-parser', 'kbn-dev-utils', + 'kbn-cli-dev-mode', 'kbn-docs-utils', 'kbn-es*', 'kbn-eslint*', diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 33b3e4a7dede62..3d44f46aca4ff7 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -158,6 +158,7 @@ /packages/kbn-ui-shared-deps/ @elastic/kibana-operations /packages/kbn-es-archiver/ @elastic/kibana-operations /packages/kbn-utils/ @elastic/kibana-operations +/packages/kbn-cli-dev-mode/ @elastic/kibana-operations /src/cli/keystore/ @elastic/kibana-operations /src/legacy/server/warnings/ @elastic/kibana-operations /.ci/es-snapshots/ @elastic/kibana-operations @@ -194,6 +195,8 @@ /packages/kbn-config/ @elastic/kibana-core /packages/kbn-logging/ @elastic/kibana-core /packages/kbn-legacy-logging/ @elastic/kibana-core +/packages/kbn-crypto/ @elastic/kibana-core +/packages/kbn-http-tools/ @elastic/kibana-core /src/legacy/server/config/ @elastic/kibana-core /src/legacy/server/http/ @elastic/kibana-core /src/legacy/server/logging/ @elastic/kibana-core diff --git a/docs/development/core/server/kibana-plugin-core-server.kibanaresponsefactory.md b/docs/development/core/server/kibana-plugin-core-server.kibanaresponsefactory.md index 551cbe3c937504..395c26a6e4bf65 100644 --- a/docs/development/core/server/kibana-plugin-core-server.kibanaresponsefactory.md +++ b/docs/development/core/server/kibana-plugin-core-server.kibanaresponsefactory.md @@ -10,10 +10,10 @@ Set of helpers used to create `KibanaResponse` to form HTTP response on an incom ```typescript kibanaResponseFactory: { - custom: | Buffer | Error | Stream | { + custom: | Error | Buffer | { message: string | Error; attributes?: Record | undefined; - } | undefined>(options: CustomHttpResponseOptions) => KibanaResponse; + } | Stream | undefined>(options: CustomHttpResponseOptions) => KibanaResponse; badRequest: (options?: ErrorHttpResponseOptions) => KibanaResponse; unauthorized: (options?: ErrorHttpResponseOptions) => KibanaResponse; forbidden: (options?: ErrorHttpResponseOptions) => KibanaResponse; diff --git a/package.json b/package.json index 99591fdc1ea405..2654c433ac5fa8 100644 --- a/package.json +++ b/package.json @@ -127,11 +127,13 @@ "@kbn/apm-utils": "link:packages/kbn-apm-utils", "@kbn/config": "link:packages/kbn-config", "@kbn/config-schema": "link:packages/kbn-config-schema", + "@kbn/crypto": "link:packages/kbn-crypto", "@kbn/i18n": "link:packages/kbn-i18n", "@kbn/interpreter": "link:packages/kbn-interpreter", "@kbn/legacy-logging": "link:packages/kbn-legacy-logging", "@kbn/logging": "link:packages/kbn-logging", "@kbn/monaco": "link:packages/kbn-monaco", + "@kbn/server-http-tools": "link:packages/kbn-server-http-tools", "@kbn/std": "link:packages/kbn-std", "@kbn/tinymath": "link:packages/kbn-tinymath", "@kbn/ui-framework": "link:packages/kbn-ui-framework", @@ -451,6 +453,7 @@ "@jest/reporters": "^26.5.2", "@kbn/babel-code-parser": "link:packages/kbn-babel-code-parser", "@kbn/babel-preset": "link:packages/kbn-babel-preset", + "@kbn/cli-dev-mode": "link:packages/kbn-cli-dev-mode", "@kbn/dev-utils": "link:packages/kbn-dev-utils", "@kbn/docs-utils": "link:packages/kbn-docs-utils", "@kbn/es": "link:packages/kbn-es", diff --git a/src/dev/cli_dev_mode/README.md b/packages/kbn-cli-dev-mode/README.md similarity index 72% rename from src/dev/cli_dev_mode/README.md rename to packages/kbn-cli-dev-mode/README.md index 397017027a52f1..6ce41249674ce2 100644 --- a/src/dev/cli_dev_mode/README.md +++ b/packages/kbn-cli-dev-mode/README.md @@ -26,8 +26,12 @@ The `DevServer` object is responsible for everything related to running and rest The `Optimizer` object manages a `@kbn/optimizer` instance, adapting its configuration and logging to the data available to the CLI. -## `BasePathProxyServer` (currently passed from core) +## `BasePathProxyServer` -The `BasePathProxyServer` is passed to the `CliDevMode` from core when the dev mode is trigged by the `--dev` flag. This proxy injects a random three character base path in the URL that Kibana is served from to help ensure that Kibana features are written to adapt to custom base path configurations from users. +This proxy injects a random three character base path in the URL that Kibana is served from to help ensure that Kibana features +are written to adapt to custom base path configurations from users. -The basePathProxy also has another important job, ensuring that requests don't fail because the server is restarting and that the browser receives front-end assets containing all saved changes. We accomplish this by observing the ready state of the `Optimizer` and `DevServer` objects and pausing all requests through the proxy until both objects report that they aren't building/restarting based on recently saved changes. \ No newline at end of file +The basePathProxy also has another important job, ensuring that requests don't fail because the server is restarting and +that the browser receives front-end assets containing all saved changes. We accomplish this by observing the ready state of +the `Optimizer` and `DevServer` objects and pausing all requests through the proxy until both objects report that +they aren't building/restarting based on recently saved changes. \ No newline at end of file diff --git a/packages/kbn-cli-dev-mode/jest.config.js b/packages/kbn-cli-dev-mode/jest.config.js new file mode 100644 index 00000000000000..d04dc571ef2a0b --- /dev/null +++ b/packages/kbn-cli-dev-mode/jest.config.js @@ -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 + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../..', + roots: ['/packages/kbn-cli-dev-mode'], +}; diff --git a/packages/kbn-cli-dev-mode/package.json b/packages/kbn-cli-dev-mode/package.json new file mode 100644 index 00000000000000..2ee9831e960842 --- /dev/null +++ b/packages/kbn-cli-dev-mode/package.json @@ -0,0 +1,26 @@ +{ + "name": "@kbn/cli-dev-mode", + "main": "./target/index.js", + "types": "./target/index.d.ts", + "version": "1.0.0", + "license": "SSPL-1.0 OR Elastic License 2.0", + "private": true, + "scripts": { + "build": "../../node_modules/.bin/tsc", + "kbn:bootstrap": "yarn build", + "kbn:watch": "yarn build --watch" + }, + "kibana": { + "devOnly": true + }, + "dependencies": { + "@kbn/config": "link:../kbn-config", + "@kbn/config-schema": "link:../kbn-config-schema", + "@kbn/logging": "link:../kbn-logging", + "@kbn/server-http-tools": "link:../kbn-server-http-tools", + "@kbn/optimizer": "link:../kbn-optimizer", + "@kbn/std": "link:../kbn-std", + "@kbn/dev-utils": "link:../kbn-dev-utils", + "@kbn/utils": "link:../kbn-utils" + } +} \ No newline at end of file diff --git a/packages/kbn-cli-dev-mode/src/base_path_proxy_server.test.ts b/packages/kbn-cli-dev-mode/src/base_path_proxy_server.test.ts new file mode 100644 index 00000000000000..c99485c2733645 --- /dev/null +++ b/packages/kbn-cli-dev-mode/src/base_path_proxy_server.test.ts @@ -0,0 +1,358 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { Server } from '@hapi/hapi'; +import { EMPTY } from 'rxjs'; +import supertest from 'supertest'; +import { + getServerOptions, + getListenerOptions, + createServer, + IHttpConfig, +} from '@kbn/server-http-tools'; +import { ByteSizeValue } from '@kbn/config-schema'; + +import { BasePathProxyServer, BasePathProxyServerOptions } from './base_path_proxy_server'; +import { DevConfig } from './config/dev_config'; +import { TestLog } from './log'; + +describe('BasePathProxyServer', () => { + let server: Server; + let proxyServer: BasePathProxyServer; + let logger: TestLog; + let config: IHttpConfig; + let basePath: string; + let proxySupertest: supertest.SuperTest; + + beforeEach(async () => { + logger = new TestLog(); + + config = { + host: '127.0.0.1', + port: 10012, + keepaliveTimeout: 1000, + socketTimeout: 1000, + cors: { + enabled: false, + allowCredentials: false, + allowOrigin: [], + }, + ssl: { enabled: false }, + maxPayload: new ByteSizeValue(1024), + }; + + const serverOptions = getServerOptions(config); + const listenerOptions = getListenerOptions(config); + server = createServer(serverOptions, listenerOptions); + + // setup and start the proxy server + const proxyConfig: IHttpConfig = { ...config, port: 10013 }; + const devConfig = new DevConfig({ basePathProxyTarget: config.port }); + proxyServer = new BasePathProxyServer(logger, proxyConfig, devConfig); + const options: BasePathProxyServerOptions = { + shouldRedirectFromOldBasePath: () => true, + delayUntil: () => EMPTY, + }; + await proxyServer.start(options); + + // set the base path or throw if for some unknown reason it is not setup + if (proxyServer.basePath == null) { + throw new Error('Invalid null base path, all tests will fail'); + } else { + basePath = proxyServer.basePath; + } + proxySupertest = supertest(`http://127.0.0.1:${proxyConfig.port}`); + }); + + afterEach(async () => { + await server.stop(); + await proxyServer.stop(); + jest.clearAllMocks(); + }); + + test('root URL will return a 302 redirect', async () => { + await proxySupertest.get('/').expect(302); + }); + + test('root URL will return a redirect location with exactly 3 characters that are a-z', async () => { + const res = await proxySupertest.get('/'); + const location = res.header.location; + expect(location).toMatch(/[a-z]{3}/); + }); + + test('forwards request with the correct path', async () => { + server.route({ + method: 'GET', + path: `${basePath}/foo/{test}`, + handler: (request, h) => { + return h.response(request.params.test); + }, + }); + await server.start(); + + await proxySupertest + .get(`${basePath}/foo/some-string`) + .expect(200) + .then((res) => { + expect(res.text).toBe('some-string'); + }); + }); + + test('forwards request with the correct query params', async () => { + server.route({ + method: 'GET', + path: `${basePath}/foo/`, + handler: (request, h) => { + return h.response(request.query); + }, + }); + + await server.start(); + + await proxySupertest + .get(`${basePath}/foo/?bar=test&quux=123`) + .expect(200) + .then((res) => { + expect(res.body).toEqual({ bar: 'test', quux: '123' }); + }); + }); + + test('forwards the request body', async () => { + server.route({ + method: 'POST', + path: `${basePath}/foo/`, + handler: (request, h) => { + return h.response(request.payload); + }, + }); + + await server.start(); + + await proxySupertest + .post(`${basePath}/foo/`) + .send({ + bar: 'test', + baz: 123, + }) + .expect(200) + .then((res) => { + expect(res.body).toEqual({ bar: 'test', baz: 123 }); + }); + }); + + test('returns the correct status code', async () => { + server.route({ + method: 'GET', + path: `${basePath}/foo/`, + handler: (request, h) => { + return h.response({ foo: 'bar' }).code(417); + }, + }); + + await server.start(); + + await proxySupertest + .get(`${basePath}/foo/`) + .expect(417) + .then((res) => { + expect(res.body).toEqual({ foo: 'bar' }); + }); + }); + + test('returns the response headers', async () => { + server.route({ + method: 'GET', + path: `${basePath}/foo/`, + handler: (request, h) => { + return h.response({ foo: 'bar' }).header('foo', 'bar'); + }, + }); + + await server.start(); + + await proxySupertest + .get(`${basePath}/foo/`) + .expect(200) + .then((res) => { + expect(res.get('foo')).toEqual('bar'); + }); + }); + + test('handles putting', async () => { + server.route({ + method: 'PUT', + path: `${basePath}/foo/`, + handler: (request, h) => { + return h.response(request.payload); + }, + }); + + await server.start(); + + await proxySupertest + .put(`${basePath}/foo/`) + .send({ + bar: 'test', + baz: 123, + }) + .expect(200) + .then((res) => { + expect(res.body).toEqual({ bar: 'test', baz: 123 }); + }); + }); + + test('handles deleting', async () => { + server.route({ + method: 'DELETE', + path: `${basePath}/foo/{test}`, + handler: (request, h) => { + return h.response(request.params.test); + }, + }); + await server.start(); + + await proxySupertest + .delete(`${basePath}/foo/some-string`) + .expect(200) + .then((res) => { + expect(res.text).toBe('some-string'); + }); + }); + + describe('with `basepath: /bar` and `rewriteBasePath: false`', () => { + beforeEach(async () => { + const configWithBasePath: IHttpConfig = { + ...config, + basePath: '/bar', + rewriteBasePath: false, + } as IHttpConfig; + + const serverOptions = getServerOptions(configWithBasePath); + const listenerOptions = getListenerOptions(configWithBasePath); + server = createServer(serverOptions, listenerOptions); + + server.route({ + method: 'GET', + path: `${basePath}/`, + handler: (request, h) => { + return h.response('value:/'); + }, + }); + server.route({ + method: 'GET', + path: `${basePath}/foo`, + handler: (request, h) => { + return h.response('value:/foo'); + }, + }); + + await server.start(); + }); + + test('/bar => 404', async () => { + await proxySupertest.get(`${basePath}/bar`).expect(404); + }); + + test('/bar/ => 404', async () => { + await proxySupertest.get(`${basePath}/bar/`).expect(404); + }); + + test('/bar/foo => 404', async () => { + await proxySupertest.get(`${basePath}/bar/foo`).expect(404); + }); + + test('/ => /', async () => { + await proxySupertest + .get(`${basePath}/`) + .expect(200) + .then((res) => { + expect(res.text).toBe('value:/'); + }); + }); + + test('/foo => /foo', async () => { + await proxySupertest + .get(`${basePath}/foo`) + .expect(200) + .then((res) => { + expect(res.text).toBe('value:/foo'); + }); + }); + }); + + describe('shouldRedirect', () => { + let proxyServerWithoutShouldRedirect: BasePathProxyServer; + let proxyWithoutShouldRedirectSupertest: supertest.SuperTest; + + beforeEach(async () => { + // setup and start a proxy server which does not use "shouldRedirectFromOldBasePath" + const proxyConfig: IHttpConfig = { ...config, port: 10004 }; + const devConfig = new DevConfig({ basePathProxyTarget: config.port }); + proxyServerWithoutShouldRedirect = new BasePathProxyServer(logger, proxyConfig, devConfig); + const options: Readonly = { + shouldRedirectFromOldBasePath: () => false, // Return false to not redirect + delayUntil: () => EMPTY, + }; + await proxyServerWithoutShouldRedirect.start(options); + proxyWithoutShouldRedirectSupertest = supertest(`http://127.0.0.1:${proxyConfig.port}`); + }); + + afterEach(async () => { + await proxyServerWithoutShouldRedirect.stop(); + }); + + test('it will do a redirect if it detects what looks like a stale or previously used base path', async () => { + const fakeBasePath = basePath !== 'abc' ? 'abc' : 'efg'; + const res = await proxySupertest.get(`/${fakeBasePath}`).expect(302); + const location = res.header.location; + expect(location).toEqual(`${basePath}/`); + }); + + test('it will NOT do a redirect if it detects what looks like a stale or previously used base path if we intentionally turn it off', async () => { + const fakeBasePath = basePath !== 'abc' ? 'abc' : 'efg'; + await proxyWithoutShouldRedirectSupertest.get(`/${fakeBasePath}`).expect(404); + }); + + test('it will NOT redirect if it detects a larger path than 3 characters', async () => { + await proxySupertest.get('/abcde').expect(404); + }); + + test('it will NOT redirect if it is not a GET verb', async () => { + const fakeBasePath = basePath !== 'abc' ? 'abc' : 'efg'; + await proxySupertest.put(`/${fakeBasePath}`).expect(404); + }); + }); + + describe('constructor option for sending in a custom basePath', () => { + let proxyServerWithFooBasePath: BasePathProxyServer; + let proxyWithFooBasePath: supertest.SuperTest; + + beforeEach(async () => { + // setup and start a proxy server which uses a basePath of "foo" + const proxyConfig = { ...config, port: 10004, basePath: '/foo' }; // <-- "foo" here in basePath + const devConfig = new DevConfig({ basePathProxyTarget: config.port }); + proxyServerWithFooBasePath = new BasePathProxyServer(logger, proxyConfig, devConfig); + const options: Readonly = { + shouldRedirectFromOldBasePath: () => true, + delayUntil: () => EMPTY, + }; + await proxyServerWithFooBasePath.start(options); + proxyWithFooBasePath = supertest(`http://127.0.0.1:${proxyConfig.port}`); + }); + + afterEach(async () => { + await proxyServerWithFooBasePath.stop(); + }); + + test('it will do a redirect to foo which is our passed in value for the configuration', async () => { + const res = await proxyWithFooBasePath.get('/bar').expect(302); + const location = res.header.location; + expect(location).toEqual('/foo/'); + }); + }); +}); diff --git a/src/core/server/http/base_path_proxy_server.ts b/packages/kbn-cli-dev-mode/src/base_path_proxy_server.ts similarity index 90% rename from src/core/server/http/base_path_proxy_server.ts rename to packages/kbn-cli-dev-mode/src/base_path_proxy_server.ts index a5ed0271893937..40841c8327cc29 100644 --- a/src/core/server/http/base_path_proxy_server.ts +++ b/packages/kbn-cli-dev-mode/src/base_path_proxy_server.ts @@ -8,21 +8,21 @@ import Url from 'url'; import { Agent as HttpsAgent, ServerOptions as TlsOptions } from 'https'; - import apm from 'elastic-apm-node'; -import { ByteSizeValue } from '@kbn/config-schema'; import { Server, Request } from '@hapi/hapi'; import HapiProxy from '@hapi/h2o2'; import { sampleSize } from 'lodash'; import * as Rx from 'rxjs'; import { take } from 'rxjs/operators'; +import { ByteSizeValue } from '@kbn/config-schema'; +import { createServer, getListenerOptions, getServerOptions } from '@kbn/server-http-tools'; -import { DevConfig } from '../dev'; -import { Logger } from '../logging'; -import { HttpConfig } from './http_config'; -import { createServer, getListenerOptions, getServerOptions } from './http_tools'; +import { DevConfig, HttpConfig } from './config'; +import { Log } from './log'; +const ONE_GIGABYTE = 1024 * 1024 * 1024; const alphabet = 'abcdefghijklmnopqrztuvwxyz'.split(''); +const getRandomBasePath = () => sampleSize(alphabet, 3).join(''); export interface BasePathProxyServerOptions { shouldRedirectFromOldBasePath: (path: string) => boolean; @@ -30,9 +30,22 @@ export interface BasePathProxyServerOptions { } export class BasePathProxyServer { + private readonly httpConfig: HttpConfig; private server?: Server; private httpsAgent?: HttpsAgent; + constructor( + private readonly log: Log, + httpConfig: HttpConfig, + private readonly devConfig: DevConfig + ) { + this.httpConfig = { + ...httpConfig, + maxPayload: new ByteSizeValue(ONE_GIGABYTE), + basePath: httpConfig.basePath ?? `/${getRandomBasePath()}`, + }; + } + public get basePath() { return this.httpConfig.basePath; } @@ -49,21 +62,8 @@ export class BasePathProxyServer { return this.httpConfig.port; } - constructor( - private readonly log: Logger, - private readonly httpConfig: HttpConfig, - private readonly devConfig: DevConfig - ) { - const ONE_GIGABYTE = 1024 * 1024 * 1024; - httpConfig.maxPayload = new ByteSizeValue(ONE_GIGABYTE); - - if (!httpConfig.basePath) { - httpConfig.basePath = `/${sampleSize(alphabet, 3).join('')}`; - } - } - - public async start(options: Readonly) { - this.log.debug('starting basepath proxy server'); + public async start(options: BasePathProxyServerOptions) { + this.log.write('starting basepath proxy server'); const serverOptions = getServerOptions(this.httpConfig); const listenerOptions = getListenerOptions(this.httpConfig); @@ -88,7 +88,7 @@ export class BasePathProxyServer { await this.server.start(); - this.log.info( + this.log.write( `basepath proxy server running at ${Url.format({ host: this.server.info.uri, pathname: this.httpConfig.basePath, @@ -101,7 +101,7 @@ export class BasePathProxyServer { return; } - this.log.debug('stopping basepath proxy server'); + this.log.write('stopping basepath proxy server'); await this.server.stop(); this.server = undefined; diff --git a/packages/kbn-cli-dev-mode/src/bootstrap.ts b/packages/kbn-cli-dev-mode/src/bootstrap.ts new file mode 100644 index 00000000000000..86a276c64f1f55 --- /dev/null +++ b/packages/kbn-cli-dev-mode/src/bootstrap.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 + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { REPO_ROOT } from '@kbn/utils'; +import { CliArgs, Env, RawConfigAdapter } from '@kbn/config'; +import { CliDevMode } from './cli_dev_mode'; +import { CliLog } from './log'; +import { convertToLogger } from './log_adapter'; +import { loadConfig } from './config'; + +interface BootstrapArgs { + configs: string[]; + cliArgs: CliArgs; + applyConfigOverrides: RawConfigAdapter; +} + +export async function bootstrapDevMode({ configs, cliArgs, applyConfigOverrides }: BootstrapArgs) { + const log = new CliLog(!!cliArgs.quiet, !!cliArgs.silent); + + const env = Env.createDefault(REPO_ROOT, { + configs, + cliArgs, + }); + + const config = await loadConfig({ + env, + logger: convertToLogger(log), + rawConfigAdapter: applyConfigOverrides, + }); + + const cliDevMode = new CliDevMode({ + cliArgs, + config, + log, + }); + + await cliDevMode.start(); +} diff --git a/src/dev/cli_dev_mode/cli_dev_mode.test.ts b/packages/kbn-cli-dev-mode/src/cli_dev_mode.test.ts similarity index 79% rename from src/dev/cli_dev_mode/cli_dev_mode.test.ts rename to packages/kbn-cli-dev-mode/src/cli_dev_mode.test.ts index 9ace543a8929bf..d5bafe7280bd92 100644 --- a/src/dev/cli_dev_mode/cli_dev_mode.test.ts +++ b/packages/kbn-cli-dev-mode/src/cli_dev_mode.test.ts @@ -7,16 +7,16 @@ */ import Path from 'path'; - +import * as Rx from 'rxjs'; import { REPO_ROOT, createAbsolutePathSerializer, createAnyInstanceSerializer, } from '@kbn/dev-utils'; -import * as Rx from 'rxjs'; import { TestLog } from './log'; -import { CliDevMode } from './cli_dev_mode'; +import { CliDevMode, SomeCliArgs } from './cli_dev_mode'; +import type { CliDevConfig } from './config'; expect.addSnapshotSerializer(createAbsolutePathSerializer()); expect.addSnapshotSerializer(createAnyInstanceSerializer(Rx.Observable, 'Rx.Observable')); @@ -31,6 +31,9 @@ const { Optimizer } = jest.requireMock('./optimizer'); jest.mock('./dev_server'); const { DevServer } = jest.requireMock('./dev_server'); +jest.mock('./base_path_proxy_server'); +const { BasePathProxyServer } = jest.requireMock('./base_path_proxy_server'); + jest.mock('@kbn/dev-utils/target/ci_stats_reporter'); const { CiStatsReporter } = jest.requireMock('@kbn/dev-utils/target/ci_stats_reporter'); @@ -41,13 +44,6 @@ jest.mock('./get_server_watch_paths', () => ({ })), })); -beforeEach(() => { - process.argv = ['node', './script', 'foo', 'bar', 'baz']; - jest.clearAllMocks(); -}); - -const log = new TestLog(); - const mockBasePathProxy = { targetPort: 9999, basePath: '/foo/bar', @@ -55,26 +51,53 @@ const mockBasePathProxy = { stop: jest.fn(), }; -const defaultOptions = { +let log: TestLog; + +beforeEach(() => { + process.argv = ['node', './script', 'foo', 'bar', 'baz']; + log = new TestLog(); + BasePathProxyServer.mockImplementation(() => mockBasePathProxy); +}); + +afterEach(() => { + jest.clearAllMocks(); + mockBasePathProxy.start.mockReset(); + mockBasePathProxy.stop.mockReset(); +}); + +const createCliArgs = (parts: Partial = {}): SomeCliArgs => ({ + basePath: false, cache: true, disableOptimizer: false, dist: true, oss: true, - pluginPaths: [], - pluginScanDirs: [Path.resolve(REPO_ROOT, 'src/plugins')], - quiet: false, - silent: false, runExamples: false, watch: true, - log, -}; + silent: false, + quiet: false, + ...parts, +}); -afterEach(() => { - log.messages.length = 0; +const createDevConfig = (parts: Partial = {}): CliDevConfig => ({ + plugins: { + pluginSearchPaths: [Path.resolve(REPO_ROOT, 'src/plugins')], + additionalPluginPaths: [], + }, + dev: { + basePathProxyTargetPort: 9000, + }, + http: {} as any, + ...parts, +}); + +const createOptions = ({ cliArgs = {} }: { cliArgs?: Partial } = {}) => ({ + cliArgs: createCliArgs(cliArgs), + config: createDevConfig(), + log, }); it('passes correct args to sub-classes', () => { - new CliDevMode(defaultOptions); + new CliDevMode(createOptions()); expect(DevServer.mock.calls).toMatchInlineSnapshot(` Array [ @@ -105,6 +128,9 @@ it('passes correct args to sub-classes', () => { "enabled": true, "oss": true, "pluginPaths": Array [], + "pluginScanDirs": Array [ + /src/plugins, + ], "quiet": false, "repoRoot": , "runExamples": false, @@ -131,33 +157,38 @@ it('passes correct args to sub-classes', () => { ], ] `); + + expect(BasePathProxyServer).not.toHaveBeenCalled(); + expect(log.messages).toMatchInlineSnapshot(`Array []`); }); it('disables the optimizer', () => { - new CliDevMode({ - ...defaultOptions, - disableOptimizer: true, - }); + new CliDevMode(createOptions({ cliArgs: { disableOptimizer: true } })); expect(Optimizer.mock.calls[0][0]).toHaveProperty('enabled', false); }); it('disables the watcher', () => { - new CliDevMode({ - ...defaultOptions, - watch: false, - }); + new CliDevMode(createOptions({ cliArgs: { watch: false } })); expect(Optimizer.mock.calls[0][0]).toHaveProperty('watch', false); expect(Watcher.mock.calls[0][0]).toHaveProperty('enabled', false); }); -it('overrides the basePath of the server when basePathProxy is defined', () => { - new CliDevMode({ - ...defaultOptions, - basePathProxy: mockBasePathProxy as any, - }); +it('enables the basePath proxy', () => { + new CliDevMode(createOptions({ cliArgs: { basePath: true } })); + + expect(BasePathProxyServer).toHaveBeenCalledTimes(1); + expect(BasePathProxyServer.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + , + Object {}, + Object { + "basePathProxyTargetPort": 9000, + }, + ] + `); expect(DevServer.mock.calls[0][0].argv).toMatchInlineSnapshot(` Array [ @@ -229,9 +260,7 @@ describe('#start()/#stop()', () => { }); it('logs a warning if basePathProxy is not passed', () => { - new CliDevMode({ - ...defaultOptions, - }).start(); + new CliDevMode(createOptions()).start(); expect(log.messages).toMatchInlineSnapshot(` Array [ @@ -261,16 +290,9 @@ describe('#start()/#stop()', () => { }); it('calls start on BasePathProxy if enabled', () => { - const basePathProxy: any = { - start: jest.fn(), - }; + new CliDevMode(createOptions({ cliArgs: { basePath: true } })).start(); - new CliDevMode({ - ...defaultOptions, - basePathProxy, - }).start(); - - expect(basePathProxy.start.mock.calls).toMatchInlineSnapshot(` + expect(mockBasePathProxy.start.mock.calls).toMatchInlineSnapshot(` Array [ Array [ Object { @@ -283,7 +305,7 @@ describe('#start()/#stop()', () => { }); it('subscribes to Optimizer#run$, Watcher#run$, and DevServer#run$', () => { - new CliDevMode(defaultOptions).start(); + new CliDevMode(createOptions()).start(); expect(optimizerRun$.observers).toHaveLength(1); expect(watcherRun$.observers).toHaveLength(1); @@ -291,10 +313,7 @@ describe('#start()/#stop()', () => { }); it('logs an error and exits the process if Optimizer#run$ errors', () => { - new CliDevMode({ - ...defaultOptions, - basePathProxy: mockBasePathProxy as any, - }).start(); + new CliDevMode(createOptions({ cliArgs: { basePath: true } })).start(); expect(processExitMock).not.toHaveBeenCalled(); optimizerRun$.error({ stack: 'Error: foo bar' }); @@ -319,10 +338,7 @@ describe('#start()/#stop()', () => { }); it('logs an error and exits the process if Watcher#run$ errors', () => { - new CliDevMode({ - ...defaultOptions, - basePathProxy: mockBasePathProxy as any, - }).start(); + new CliDevMode(createOptions({ cliArgs: { basePath: true } })).start(); expect(processExitMock).not.toHaveBeenCalled(); watcherRun$.error({ stack: 'Error: foo bar' }); @@ -347,10 +363,7 @@ describe('#start()/#stop()', () => { }); it('logs an error and exits the process if DevServer#run$ errors', () => { - new CliDevMode({ - ...defaultOptions, - basePathProxy: mockBasePathProxy as any, - }).start(); + new CliDevMode(createOptions({ cliArgs: { basePath: true } })).start(); expect(processExitMock).not.toHaveBeenCalled(); devServerRun$.error({ stack: 'Error: foo bar' }); @@ -376,10 +389,7 @@ describe('#start()/#stop()', () => { it('throws if start() has already been called', () => { expect(() => { - const devMode = new CliDevMode({ - ...defaultOptions, - basePathProxy: mockBasePathProxy as any, - }); + const devMode = new CliDevMode(createOptions({ cliArgs: { basePath: true } })); devMode.start(); devMode.start(); @@ -387,10 +397,7 @@ describe('#start()/#stop()', () => { }); it('unsubscribes from all observables and stops basePathProxy when stopped', () => { - const devMode = new CliDevMode({ - ...defaultOptions, - basePathProxy: mockBasePathProxy as any, - }); + const devMode = new CliDevMode(createOptions({ cliArgs: { basePath: true } })); devMode.start(); devMode.stop(); diff --git a/src/dev/cli_dev_mode/cli_dev_mode.ts b/packages/kbn-cli-dev-mode/src/cli_dev_mode.ts similarity index 83% rename from src/dev/cli_dev_mode/cli_dev_mode.ts rename to packages/kbn-cli-dev-mode/src/cli_dev_mode.ts index f4f95f20daeef3..94dbcb9654e8ae 100644 --- a/src/dev/cli_dev_mode/cli_dev_mode.ts +++ b/packages/kbn-cli-dev-mode/src/cli_dev_mode.ts @@ -7,8 +7,6 @@ */ import Path from 'path'; - -import { REPO_ROOT, CiStatsReporter } from '@kbn/dev-utils'; import * as Rx from 'rxjs'; import { map, @@ -20,24 +18,32 @@ import { switchMap, concatMap, } from 'rxjs/operators'; - -import { CliArgs } from '../../core/server/config'; -import { LegacyConfig } from '../../core/server/legacy'; -import { BasePathProxyServer } from '../../core/server/http'; +import { CliArgs } from '@kbn/config'; +import { REPO_ROOT, CiStatsReporter } from '@kbn/dev-utils'; import { Log, CliLog } from './log'; import { Optimizer } from './optimizer'; import { DevServer } from './dev_server'; import { Watcher } from './watcher'; +import { BasePathProxyServer } from './base_path_proxy_server'; import { shouldRedirectFromOldBasePath } from './should_redirect_from_old_base_path'; import { getServerWatchPaths } from './get_server_watch_paths'; +import { CliDevConfig } from './config'; // timeout where the server is allowed to exit gracefully const GRACEFUL_TIMEOUT = 5000; export type SomeCliArgs = Pick< CliArgs, - 'quiet' | 'silent' | 'disableOptimizer' | 'watch' | 'oss' | 'runExamples' | 'cache' | 'dist' + | 'quiet' + | 'silent' + | 'disableOptimizer' + | 'watch' + | 'oss' + | 'runExamples' + | 'cache' + | 'dist' + | 'basePath' >; export interface CliDevModeOptions { @@ -76,49 +82,28 @@ const firstAllTrue = (...sources: Array>) => * */ export class CliDevMode { - static fromCoreServices( - cliArgs: SomeCliArgs, - config: LegacyConfig, - basePathProxy?: BasePathProxyServer - ) { - new CliDevMode({ - quiet: !!cliArgs.quiet, - silent: !!cliArgs.silent, - cache: !!cliArgs.cache, - disableOptimizer: !!cliArgs.disableOptimizer, - dist: !!cliArgs.dist, - oss: !!cliArgs.oss, - runExamples: !!cliArgs.runExamples, - pluginPaths: config.get('plugins.paths'), - pluginScanDirs: config.get('plugins.scanDirs'), - watch: !!cliArgs.watch, - basePathProxy, - }).start(); - } private readonly log: Log; private readonly basePathProxy?: BasePathProxyServer; private readonly watcher: Watcher; private readonly devServer: DevServer; private readonly optimizer: Optimizer; private startTime?: number; - private subscription?: Rx.Subscription; - constructor(options: CliDevModeOptions) { - this.basePathProxy = options.basePathProxy; - this.log = options.log || new CliLog(!!options.quiet, !!options.silent); + constructor({ cliArgs, config, log }: { cliArgs: SomeCliArgs; config: CliDevConfig; log?: Log }) { + this.log = log || new CliLog(!!cliArgs.quiet, !!cliArgs.silent); + + if (cliArgs.basePath) { + this.basePathProxy = new BasePathProxyServer(this.log, config.http, config.dev); + } const { watchPaths, ignorePaths } = getServerWatchPaths({ - pluginPaths: options.pluginPaths ?? [], - pluginScanDirs: [ - ...(options.pluginScanDirs ?? []), - Path.resolve(REPO_ROOT, 'src/plugins'), - Path.resolve(REPO_ROOT, 'x-pack/plugins'), - ], + pluginPaths: config.plugins.additionalPluginPaths, + pluginScanDirs: config.plugins.pluginSearchPaths, }); this.watcher = new Watcher({ - enabled: !!options.watch, + enabled: !!cliArgs.watch, log: this.log, cwd: REPO_ROOT, paths: watchPaths, @@ -133,10 +118,10 @@ export class CliDevMode { script: Path.resolve(REPO_ROOT, 'scripts/kibana'), argv: [ ...process.argv.slice(2).filter((v) => v !== '--no-watch'), - ...(options.basePathProxy + ...(this.basePathProxy ? [ - `--server.port=${options.basePathProxy.targetPort}`, - `--server.basePath=${options.basePathProxy.basePath}`, + `--server.port=${this.basePathProxy.targetPort}`, + `--server.basePath=${this.basePathProxy.basePath}`, '--server.rewriteBasePath=true', ] : []), @@ -153,16 +138,17 @@ export class CliDevMode { }); this.optimizer = new Optimizer({ - enabled: !options.disableOptimizer, + enabled: !cliArgs.disableOptimizer, repoRoot: REPO_ROOT, - oss: options.oss, - pluginPaths: options.pluginPaths, - runExamples: options.runExamples, - cache: options.cache, - dist: options.dist, - quiet: options.quiet, - silent: options.silent, - watch: options.watch, + oss: cliArgs.oss, + pluginPaths: config.plugins.additionalPluginPaths, + pluginScanDirs: config.plugins.pluginSearchPaths, + runExamples: cliArgs.runExamples, + cache: cliArgs.cache, + dist: cliArgs.dist, + quiet: !!cliArgs.quiet, + silent: !!cliArgs.silent, + watch: cliArgs.watch, }); } diff --git a/packages/kbn-cli-dev-mode/src/config/dev_config.ts b/packages/kbn-cli-dev-mode/src/config/dev_config.ts new file mode 100644 index 00000000000000..ddb54bb8f3f7c9 --- /dev/null +++ b/packages/kbn-cli-dev-mode/src/config/dev_config.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 + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { schema, TypeOf } from '@kbn/config-schema'; + +export const devConfigSchema = schema.object( + { + basePathProxyTarget: schema.number({ + defaultValue: 5603, + }), + }, + { unknowns: 'ignore' } +); + +export type DevConfigType = TypeOf; + +export class DevConfig { + public basePathProxyTargetPort: number; + + constructor(rawConfig: DevConfigType) { + this.basePathProxyTargetPort = rawConfig.basePathProxyTarget; + } +} diff --git a/packages/kbn-cli-dev-mode/src/config/http_config.ts b/packages/kbn-cli-dev-mode/src/config/http_config.ts new file mode 100644 index 00000000000000..34f208c28df680 --- /dev/null +++ b/packages/kbn-cli-dev-mode/src/config/http_config.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 + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ByteSizeValue, schema, TypeOf } from '@kbn/config-schema'; +import { ICorsConfig, IHttpConfig, ISslConfig, SslConfig, sslSchema } from '@kbn/server-http-tools'; + +export const httpConfigSchema = schema.object( + { + host: schema.string({ + defaultValue: 'localhost', + hostname: true, + }), + basePath: schema.maybe(schema.string()), + port: schema.number({ + defaultValue: 5601, + }), + maxPayload: schema.byteSize({ + defaultValue: '1048576b', + }), + keepaliveTimeout: schema.number({ + defaultValue: 120000, + }), + socketTimeout: schema.number({ + defaultValue: 120000, + }), + cors: schema.object({ + enabled: schema.boolean({ defaultValue: false }), + allowCredentials: schema.boolean({ defaultValue: false }), + allowOrigin: schema.arrayOf(schema.string(), { + defaultValue: ['*'], + }), + }), + ssl: sslSchema, + }, + { unknowns: 'ignore' } +); + +export type HttpConfigType = TypeOf; + +export class HttpConfig implements IHttpConfig { + basePath?: string; + host: string; + port: number; + maxPayload: ByteSizeValue; + keepaliveTimeout: number; + socketTimeout: number; + cors: ICorsConfig; + ssl: ISslConfig; + + constructor(rawConfig: HttpConfigType) { + this.basePath = rawConfig.basePath; + this.host = rawConfig.host; + this.port = rawConfig.port; + this.maxPayload = rawConfig.maxPayload; + this.keepaliveTimeout = rawConfig.keepaliveTimeout; + this.socketTimeout = rawConfig.socketTimeout; + this.cors = rawConfig.cors; + this.ssl = new SslConfig(rawConfig.ssl); + } +} diff --git a/packages/kbn-cli-dev-mode/src/config/index.ts b/packages/kbn-cli-dev-mode/src/config/index.ts new file mode 100644 index 00000000000000..89f6d647ef4f51 --- /dev/null +++ b/packages/kbn-cli-dev-mode/src/config/index.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 + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export type { DevConfig } from './dev_config'; +export type { PluginsConfig } from './plugins_config'; +export type { HttpConfig } from './http_config'; +export type { CliDevConfig } from './types'; +export { loadConfig } from './load_config'; diff --git a/packages/kbn-cli-dev-mode/src/config/load_config.ts b/packages/kbn-cli-dev-mode/src/config/load_config.ts new file mode 100644 index 00000000000000..46129834ca2d9e --- /dev/null +++ b/packages/kbn-cli-dev-mode/src/config/load_config.ts @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { Env, RawConfigService, ConfigService, RawConfigAdapter } from '@kbn/config'; +import { Logger } from '@kbn/logging'; +import { devConfigSchema, DevConfig, DevConfigType } from './dev_config'; +import { httpConfigSchema, HttpConfig, HttpConfigType } from './http_config'; +import { pluginsConfigSchema, PluginsConfig, PluginsConfigType } from './plugins_config'; +import { CliDevConfig } from './types'; + +export const loadConfig = async ({ + env, + logger, + rawConfigAdapter, +}: { + env: Env; + logger: Logger; + rawConfigAdapter: RawConfigAdapter; +}): Promise => { + const rawConfigService = new RawConfigService(env.configs, rawConfigAdapter); + rawConfigService.loadConfig(); + + const configService = new ConfigService(rawConfigService, env, logger); + configService.setSchema('dev', devConfigSchema); + configService.setSchema('plugins', pluginsConfigSchema); + configService.setSchema('http', httpConfigSchema); + + await configService.validate(); + + const devConfig = configService.atPathSync('dev'); + const pluginsConfig = configService.atPathSync('plugins'); + const httpConfig = configService.atPathSync('http'); + + return { + dev: new DevConfig(devConfig), + plugins: new PluginsConfig(pluginsConfig, env), + http: new HttpConfig(httpConfig), + }; +}; diff --git a/packages/kbn-cli-dev-mode/src/config/plugins_config.ts b/packages/kbn-cli-dev-mode/src/config/plugins_config.ts new file mode 100644 index 00000000000000..7c7fa8902edb3b --- /dev/null +++ b/packages/kbn-cli-dev-mode/src/config/plugins_config.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { schema, TypeOf } from '@kbn/config-schema'; +import { Env } from '@kbn/config'; + +export const pluginsConfigSchema = schema.object( + { + paths: schema.arrayOf(schema.string(), { defaultValue: [] }), + }, + { unknowns: 'ignore' } +); + +export type PluginsConfigType = TypeOf; + +/** @internal */ +export class PluginsConfig { + /** + * Defines directories that we should scan for the plugin subdirectories. + */ + public readonly pluginSearchPaths: string[]; + + /** + * Defines directories where an additional plugin exists. + */ + public readonly additionalPluginPaths: string[]; + + constructor(rawConfig: PluginsConfigType, env: Env) { + this.pluginSearchPaths = [...env.pluginSearchPaths]; + this.additionalPluginPaths = rawConfig.paths; + } +} diff --git a/packages/kbn-cli-dev-mode/src/config/types.ts b/packages/kbn-cli-dev-mode/src/config/types.ts new file mode 100644 index 00000000000000..017442e09bd0df --- /dev/null +++ b/packages/kbn-cli-dev-mode/src/config/types.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 + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { DevConfig } from './dev_config'; +import type { HttpConfig } from './http_config'; +import type { PluginsConfig } from './plugins_config'; + +export interface CliDevConfig { + dev: DevConfig; + http: HttpConfig; + plugins: PluginsConfig; +} diff --git a/src/dev/cli_dev_mode/dev_server.test.ts b/packages/kbn-cli-dev-mode/src/dev_server.test.ts similarity index 100% rename from src/dev/cli_dev_mode/dev_server.test.ts rename to packages/kbn-cli-dev-mode/src/dev_server.test.ts diff --git a/src/dev/cli_dev_mode/dev_server.ts b/packages/kbn-cli-dev-mode/src/dev_server.ts similarity index 100% rename from src/dev/cli_dev_mode/dev_server.ts rename to packages/kbn-cli-dev-mode/src/dev_server.ts diff --git a/src/dev/cli_dev_mode/get_active_inspect_flag.ts b/packages/kbn-cli-dev-mode/src/get_active_inspect_flag.ts similarity index 100% rename from src/dev/cli_dev_mode/get_active_inspect_flag.ts rename to packages/kbn-cli-dev-mode/src/get_active_inspect_flag.ts diff --git a/src/dev/cli_dev_mode/get_server_watch_paths.test.ts b/packages/kbn-cli-dev-mode/src/get_server_watch_paths.test.ts similarity index 100% rename from src/dev/cli_dev_mode/get_server_watch_paths.test.ts rename to packages/kbn-cli-dev-mode/src/get_server_watch_paths.test.ts diff --git a/src/dev/cli_dev_mode/get_server_watch_paths.ts b/packages/kbn-cli-dev-mode/src/get_server_watch_paths.ts similarity index 87% rename from src/dev/cli_dev_mode/get_server_watch_paths.ts rename to packages/kbn-cli-dev-mode/src/get_server_watch_paths.ts index 46aa15659a5139..53aa53b5aa63a5 100644 --- a/src/dev/cli_dev_mode/get_server_watch_paths.ts +++ b/packages/kbn-cli-dev-mode/src/get_server_watch_paths.ts @@ -47,15 +47,7 @@ export function getServerWatchPaths({ pluginPaths, pluginScanDirs }: Options) { ...pluginScanDirs, ].map((path) => Path.resolve(path)) ) - ); - - for (const watchPath of watchPaths) { - if (!Fs.existsSync(fromRoot(watchPath))) { - throw new Error( - `A watch directory [${watchPath}] does not exist, which will cause chokidar to fail. Either make sure the directory exists or remove it as a watch source in the ClusterManger` - ); - } - } + ).filter((path) => Fs.existsSync(fromRoot(path))); const ignorePaths = [ /[\\\/](\..*|node_modules|bower_components|target|public|__[a-z0-9_]+__|coverage)([\\\/]|$)/, diff --git a/src/dev/cli_dev_mode/index.ts b/packages/kbn-cli-dev-mode/src/index.ts similarity index 86% rename from src/dev/cli_dev_mode/index.ts rename to packages/kbn-cli-dev-mode/src/index.ts index db46957504b112..98b52087f231a3 100644 --- a/src/dev/cli_dev_mode/index.ts +++ b/packages/kbn-cli-dev-mode/src/index.ts @@ -6,5 +6,4 @@ * Side Public License, v 1. */ -export * from './cli_dev_mode'; -export * from './log'; +export { bootstrapDevMode } from './bootstrap'; diff --git a/src/dev/cli_dev_mode/log.ts b/packages/kbn-cli-dev-mode/src/log.ts similarity index 100% rename from src/dev/cli_dev_mode/log.ts rename to packages/kbn-cli-dev-mode/src/log.ts diff --git a/packages/kbn-cli-dev-mode/src/log_adapter.ts b/packages/kbn-cli-dev-mode/src/log_adapter.ts new file mode 100644 index 00000000000000..65161fcc56e0e6 --- /dev/null +++ b/packages/kbn-cli-dev-mode/src/log_adapter.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 + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { Logger } from '@kbn/logging'; +import { Log } from './log'; + +export const convertToLogger = (cliLog: Log): Logger => { + const getErrorMessage = (msgOrError: string | Error): string => { + return typeof msgOrError === 'string' ? msgOrError : msgOrError.message; + }; + + const adapter: Logger = { + trace: (message) => cliLog.write(message), + debug: (message) => cliLog.write(message), + info: (message) => cliLog.write(message), + warn: (msgOrError) => cliLog.warn('warning', getErrorMessage(msgOrError)), + error: (msgOrError) => cliLog.bad('error', getErrorMessage(msgOrError)), + fatal: (msgOrError) => cliLog.bad('fatal', getErrorMessage(msgOrError)), + log: (record) => cliLog.write(record.message), + get: () => adapter, + }; + return adapter; +}; diff --git a/src/dev/cli_dev_mode/optimizer.test.ts b/packages/kbn-cli-dev-mode/src/optimizer.test.ts similarity index 96% rename from src/dev/cli_dev_mode/optimizer.test.ts rename to packages/kbn-cli-dev-mode/src/optimizer.test.ts index 409ad1a455a57b..c270a00329897a 100644 --- a/src/dev/cli_dev_mode/optimizer.test.ts +++ b/packages/kbn-cli-dev-mode/src/optimizer.test.ts @@ -43,6 +43,7 @@ const defaultOptions: Options = { dist: true, oss: true, pluginPaths: ['/some/dir'], + pluginScanDirs: ['/some-scan-path'], quiet: true, silent: true, repoRoot: '/app', @@ -83,6 +84,7 @@ it('uses options to create valid OptimizerConfig', () => { runExamples: false, oss: false, pluginPaths: [], + pluginScanDirs: [], repoRoot: '/foo/bar', watch: false, }); @@ -99,6 +101,9 @@ it('uses options to create valid OptimizerConfig', () => { "pluginPaths": Array [ "/some/dir", ], + "pluginScanDirs": Array [ + "/some-scan-path", + ], "repoRoot": "/app", "watch": true, }, @@ -111,6 +116,7 @@ it('uses options to create valid OptimizerConfig', () => { "includeCoreBundle": true, "oss": false, "pluginPaths": Array [], + "pluginScanDirs": Array [], "repoRoot": "/foo/bar", "watch": false, }, diff --git a/src/dev/cli_dev_mode/optimizer.ts b/packages/kbn-cli-dev-mode/src/optimizer.ts similarity index 97% rename from src/dev/cli_dev_mode/optimizer.ts rename to packages/kbn-cli-dev-mode/src/optimizer.ts index 771da21e6151b8..5e2f16fcf7daa8 100644 --- a/src/dev/cli_dev_mode/optimizer.ts +++ b/packages/kbn-cli-dev-mode/src/optimizer.ts @@ -31,6 +31,7 @@ export interface Options { oss: boolean; runExamples: boolean; pluginPaths: string[]; + pluginScanDirs: string[]; writeLogTo?: Writable; } @@ -56,6 +57,7 @@ export class Optimizer { oss: options.oss, examples: options.runExamples, pluginPaths: options.pluginPaths, + pluginScanDirs: options.pluginScanDirs, }); const dim = Chalk.dim('np bld'); diff --git a/src/dev/cli_dev_mode/should_redirect_from_old_base_path.test.ts b/packages/kbn-cli-dev-mode/src/should_redirect_from_old_base_path.test.ts similarity index 100% rename from src/dev/cli_dev_mode/should_redirect_from_old_base_path.test.ts rename to packages/kbn-cli-dev-mode/src/should_redirect_from_old_base_path.test.ts diff --git a/src/dev/cli_dev_mode/should_redirect_from_old_base_path.ts b/packages/kbn-cli-dev-mode/src/should_redirect_from_old_base_path.ts similarity index 100% rename from src/dev/cli_dev_mode/should_redirect_from_old_base_path.ts rename to packages/kbn-cli-dev-mode/src/should_redirect_from_old_base_path.ts diff --git a/src/dev/cli_dev_mode/test_helpers.ts b/packages/kbn-cli-dev-mode/src/test_helpers.ts similarity index 100% rename from src/dev/cli_dev_mode/test_helpers.ts rename to packages/kbn-cli-dev-mode/src/test_helpers.ts diff --git a/src/dev/cli_dev_mode/using_server_process.ts b/packages/kbn-cli-dev-mode/src/using_server_process.ts similarity index 100% rename from src/dev/cli_dev_mode/using_server_process.ts rename to packages/kbn-cli-dev-mode/src/using_server_process.ts diff --git a/src/dev/cli_dev_mode/watcher.test.ts b/packages/kbn-cli-dev-mode/src/watcher.test.ts similarity index 100% rename from src/dev/cli_dev_mode/watcher.test.ts rename to packages/kbn-cli-dev-mode/src/watcher.test.ts diff --git a/src/dev/cli_dev_mode/watcher.ts b/packages/kbn-cli-dev-mode/src/watcher.ts similarity index 100% rename from src/dev/cli_dev_mode/watcher.ts rename to packages/kbn-cli-dev-mode/src/watcher.ts diff --git a/packages/kbn-cli-dev-mode/tsconfig.json b/packages/kbn-cli-dev-mode/tsconfig.json new file mode 100644 index 00000000000000..b2bdaf8ceea36e --- /dev/null +++ b/packages/kbn-cli-dev-mode/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "declaration": true, + "outDir": "./target", + "declarationMap": true, + "types": ["jest", "node"] + }, + "include": ["./src/**/*.ts"], + "exclude": ["target"] +} diff --git a/packages/kbn-config/src/__mocks__/env.ts b/packages/kbn-config/src/__mocks__/env.ts index e3b3106933f1e9..6f05f8f1f5a45a 100644 --- a/packages/kbn-config/src/__mocks__/env.ts +++ b/packages/kbn-config/src/__mocks__/env.ts @@ -30,6 +30,5 @@ export function getEnvOptions(options: DeepPartial = {}): EnvOptions runExamples: false, ...(options.cliArgs || {}), }, - isDevCliParent: options.isDevCliParent !== undefined ? options.isDevCliParent : false, }; } diff --git a/packages/kbn-config/src/__snapshots__/env.test.ts.snap b/packages/kbn-config/src/__snapshots__/env.test.ts.snap index fae14529a4af3d..570ed948774cc1 100644 --- a/packages/kbn-config/src/__snapshots__/env.test.ts.snap +++ b/packages/kbn-config/src/__snapshots__/env.test.ts.snap @@ -21,7 +21,6 @@ Env { "/some/other/path/some-kibana.yml", ], "homeDir": "/test/kibanaRoot", - "isDevCliParent": false, "logDir": "/test/kibanaRoot/log", "mode": Object { "dev": true, @@ -65,7 +64,6 @@ Env { "/some/other/path/some-kibana.yml", ], "homeDir": "/test/kibanaRoot", - "isDevCliParent": false, "logDir": "/test/kibanaRoot/log", "mode": Object { "dev": false, @@ -108,7 +106,6 @@ Env { "/test/cwd/config/kibana.yml", ], "homeDir": "/test/kibanaRoot", - "isDevCliParent": true, "logDir": "/test/kibanaRoot/log", "mode": Object { "dev": true, @@ -151,7 +148,6 @@ Env { "/some/other/path/some-kibana.yml", ], "homeDir": "/test/kibanaRoot", - "isDevCliParent": false, "logDir": "/test/kibanaRoot/log", "mode": Object { "dev": false, @@ -194,7 +190,6 @@ Env { "/some/other/path/some-kibana.yml", ], "homeDir": "/test/kibanaRoot", - "isDevCliParent": false, "logDir": "/test/kibanaRoot/log", "mode": Object { "dev": false, @@ -237,7 +232,6 @@ Env { "/some/other/path/some-kibana.yml", ], "homeDir": "/some/home/dir", - "isDevCliParent": false, "logDir": "/some/home/dir/log", "mode": Object { "dev": false, diff --git a/packages/kbn-config/src/env.test.ts b/packages/kbn-config/src/env.test.ts index 09d44f31cf8d55..b9e97514c2dffb 100644 --- a/packages/kbn-config/src/env.test.ts +++ b/packages/kbn-config/src/env.test.ts @@ -36,7 +36,6 @@ test('correctly creates default environment in dev mode.', () => { REPO_ROOT, getEnvOptions({ configs: ['/test/cwd/config/kibana.yml'], - isDevCliParent: true, }) ); diff --git a/packages/kbn-config/src/env.ts b/packages/kbn-config/src/env.ts index b6ff5e3b5aab22..c4845ab429c573 100644 --- a/packages/kbn-config/src/env.ts +++ b/packages/kbn-config/src/env.ts @@ -15,7 +15,6 @@ import { PackageInfo, EnvironmentMode } from './types'; export interface EnvOptions { configs: string[]; cliArgs: CliArgs; - isDevCliParent: boolean; } /** @internal */ @@ -89,12 +88,6 @@ export class Env { */ public readonly configs: readonly string[]; - /** - * Indicates that this Kibana instance is running in the parent process of the dev cli. - * @internal - */ - public readonly isDevCliParent: boolean; - /** * @internal */ @@ -111,7 +104,6 @@ export class Env { this.cliArgs = Object.freeze(options.cliArgs); this.configs = Object.freeze(options.configs); - this.isDevCliParent = options.isDevCliParent; const isDevMode = this.cliArgs.dev || this.cliArgs.envName === 'development'; this.mode = Object.freeze({ diff --git a/packages/kbn-config/src/index.ts b/packages/kbn-config/src/index.ts index 24f271c979f321..8b0bdb0befbfdb 100644 --- a/packages/kbn-config/src/index.ts +++ b/packages/kbn-config/src/index.ts @@ -16,7 +16,12 @@ export { ConfigDeprecationWithContext, } from './deprecation'; -export { RawConfigurationProvider, RawConfigService, getConfigFromFiles } from './raw'; +export { + RawConfigurationProvider, + RawConfigService, + RawConfigAdapter, + getConfigFromFiles, +} from './raw'; export { ConfigService, IConfigService } from './config_service'; export { Config, ConfigPath, isConfigPath, hasConfigPathIntersection } from './config'; diff --git a/packages/kbn-config/src/raw/index.ts b/packages/kbn-config/src/raw/index.ts index 8f65e7877ba56f..01ad83728aa085 100644 --- a/packages/kbn-config/src/raw/index.ts +++ b/packages/kbn-config/src/raw/index.ts @@ -6,5 +6,5 @@ * Side Public License, v 1. */ -export { RawConfigService, RawConfigurationProvider } from './raw_config_service'; +export { RawConfigService, RawConfigurationProvider, RawConfigAdapter } from './raw_config_service'; export { getConfigFromFiles } from './read_config'; diff --git a/packages/kbn-config/src/raw/raw_config_service.ts b/packages/kbn-config/src/raw/raw_config_service.ts index af901f2b3d28e3..cce1132bebdb0e 100644 --- a/packages/kbn-config/src/raw/raw_config_service.ts +++ b/packages/kbn-config/src/raw/raw_config_service.ts @@ -13,7 +13,7 @@ import typeDetect from 'type-detect'; import { getConfigFromFiles } from './read_config'; -type RawConfigAdapter = (rawConfig: Record) => Record; +export type RawConfigAdapter = (rawConfig: Record) => Record; export type RawConfigurationProvider = Pick; diff --git a/packages/kbn-crypto/README.md b/packages/kbn-crypto/README.md new file mode 100644 index 00000000000000..4404c22eba37c5 --- /dev/null +++ b/packages/kbn-crypto/README.md @@ -0,0 +1,3 @@ +# @kbn/crypto + +Crypto tools and utilities for Kibana \ No newline at end of file diff --git a/packages/kbn-crypto/jest.config.js b/packages/kbn-crypto/jest.config.js new file mode 100644 index 00000000000000..811b87e5ed0f6e --- /dev/null +++ b/packages/kbn-crypto/jest.config.js @@ -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 + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../..', + roots: ['/packages/kbn-crypto'], +}; diff --git a/packages/kbn-crypto/package.json b/packages/kbn-crypto/package.json new file mode 100644 index 00000000000000..6c7b3f3b0c719b --- /dev/null +++ b/packages/kbn-crypto/package.json @@ -0,0 +1,16 @@ +{ + "name": "@kbn/crypto", + "version": "1.0.0", + "private": true, + "license": "SSPL-1.0 OR Elastic License 2.0", + "main": "./target/index.js", + "scripts": { + "build": "../../node_modules/.bin/tsc", + "kbn:bootstrap": "yarn build", + "kbn:watch": "yarn build --watch" + }, + "dependencies": {}, + "devDependencies": { + "@kbn/dev-utils": "link:../kbn-dev-utils" + } +} \ No newline at end of file diff --git a/src/core/server/utils/crypto/__fixtures__/README.md b/packages/kbn-crypto/src/__fixtures__/README.md similarity index 100% rename from src/core/server/utils/crypto/__fixtures__/README.md rename to packages/kbn-crypto/src/__fixtures__/README.md diff --git a/src/core/server/utils/crypto/__fixtures__/index.ts b/packages/kbn-crypto/src/__fixtures__/index.ts similarity index 100% rename from src/core/server/utils/crypto/__fixtures__/index.ts rename to packages/kbn-crypto/src/__fixtures__/index.ts diff --git a/src/core/server/utils/crypto/__fixtures__/no_ca.p12 b/packages/kbn-crypto/src/__fixtures__/no_ca.p12 similarity index 100% rename from src/core/server/utils/crypto/__fixtures__/no_ca.p12 rename to packages/kbn-crypto/src/__fixtures__/no_ca.p12 diff --git a/src/core/server/utils/crypto/__fixtures__/no_cert.p12 b/packages/kbn-crypto/src/__fixtures__/no_cert.p12 similarity index 100% rename from src/core/server/utils/crypto/__fixtures__/no_cert.p12 rename to packages/kbn-crypto/src/__fixtures__/no_cert.p12 diff --git a/src/core/server/utils/crypto/__fixtures__/no_key.p12 b/packages/kbn-crypto/src/__fixtures__/no_key.p12 similarity index 100% rename from src/core/server/utils/crypto/__fixtures__/no_key.p12 rename to packages/kbn-crypto/src/__fixtures__/no_key.p12 diff --git a/src/core/server/utils/crypto/__fixtures__/two_cas.p12 b/packages/kbn-crypto/src/__fixtures__/two_cas.p12 similarity index 100% rename from src/core/server/utils/crypto/__fixtures__/two_cas.p12 rename to packages/kbn-crypto/src/__fixtures__/two_cas.p12 diff --git a/src/core/server/utils/crypto/__fixtures__/two_keys.p12 b/packages/kbn-crypto/src/__fixtures__/two_keys.p12 similarity index 100% rename from src/core/server/utils/crypto/__fixtures__/two_keys.p12 rename to packages/kbn-crypto/src/__fixtures__/two_keys.p12 diff --git a/src/core/server/utils/crypto/index.ts b/packages/kbn-crypto/src/index.ts similarity index 100% rename from src/core/server/utils/crypto/index.ts rename to packages/kbn-crypto/src/index.ts diff --git a/src/core/server/utils/crypto/pkcs12.test.ts b/packages/kbn-crypto/src/pkcs12.test.ts similarity index 99% rename from src/core/server/utils/crypto/pkcs12.test.ts rename to packages/kbn-crypto/src/pkcs12.test.ts index 8c6e5bae3b9c1e..ba8eb6554f7b8b 100644 --- a/src/core/server/utils/crypto/pkcs12.test.ts +++ b/packages/kbn-crypto/src/pkcs12.test.ts @@ -18,7 +18,7 @@ import { import { NO_CA_PATH, NO_CERT_PATH, NO_KEY_PATH, TWO_CAS_PATH, TWO_KEYS_PATH } from './__fixtures__'; import { readFileSync } from 'fs'; -import { readPkcs12Keystore, Pkcs12ReadResult, readPkcs12Truststore } from './index'; +import { readPkcs12Keystore, Pkcs12ReadResult, readPkcs12Truststore } from './pkcs12'; const reformatPem = (pem: string) => { // ensure consistency in line endings when comparing two PEM files diff --git a/src/core/server/utils/crypto/pkcs12.ts b/packages/kbn-crypto/src/pkcs12.ts similarity index 100% rename from src/core/server/utils/crypto/pkcs12.ts rename to packages/kbn-crypto/src/pkcs12.ts diff --git a/src/core/server/utils/crypto/sha256.test.ts b/packages/kbn-crypto/src/sha256.test.ts similarity index 100% rename from src/core/server/utils/crypto/sha256.test.ts rename to packages/kbn-crypto/src/sha256.test.ts diff --git a/src/core/server/utils/crypto/sha256.ts b/packages/kbn-crypto/src/sha256.ts similarity index 100% rename from src/core/server/utils/crypto/sha256.ts rename to packages/kbn-crypto/src/sha256.ts diff --git a/packages/kbn-crypto/tsconfig.json b/packages/kbn-crypto/tsconfig.json new file mode 100644 index 00000000000000..e9dd6313e6f79e --- /dev/null +++ b/packages/kbn-crypto/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target", + "declaration": true, + "declarationMap": true + }, + "include": [ + "src/**/*" + ] +} diff --git a/packages/kbn-server-http-tools/README.md b/packages/kbn-server-http-tools/README.md new file mode 100644 index 00000000000000..c53b7b85354383 --- /dev/null +++ b/packages/kbn-server-http-tools/README.md @@ -0,0 +1,3 @@ +# @kbn/http-tools + +Http utilities for core and the basepath server \ No newline at end of file diff --git a/packages/kbn-server-http-tools/jest.config.js b/packages/kbn-server-http-tools/jest.config.js new file mode 100644 index 00000000000000..e409c235546227 --- /dev/null +++ b/packages/kbn-server-http-tools/jest.config.js @@ -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 + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../..', + roots: ['/packages/kbn-server-http-tools'], +}; diff --git a/packages/kbn-server-http-tools/package.json b/packages/kbn-server-http-tools/package.json new file mode 100644 index 00000000000000..a8f99689f33354 --- /dev/null +++ b/packages/kbn-server-http-tools/package.json @@ -0,0 +1,20 @@ +{ + "name": "@kbn/server-http-tools", + "main": "./target/index.js", + "version": "1.0.0", + "license": "SSPL-1.0 OR Elastic License 2.0", + "private": true, + "scripts": { + "build": "rm -rf target && ../../node_modules/.bin/tsc", + "kbn:bootstrap": "yarn build", + "kbn:watch": "yarn build --watch" + }, + "dependencies": { + "@kbn/config-schema": "link:../kbn-config-schema", + "@kbn/crypto": "link:../kbn-crypto", + "@kbn/std": "link:../kbn-std" + }, + "devDependencies": { + "@kbn/utility-types": "link:../kbn-utility-types" + } +} \ No newline at end of file diff --git a/packages/kbn-server-http-tools/src/create_server.ts b/packages/kbn-server-http-tools/src/create_server.ts new file mode 100644 index 00000000000000..4752e342d5d3e3 --- /dev/null +++ b/packages/kbn-server-http-tools/src/create_server.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { Server, ServerOptions } from '@hapi/hapi'; +import { ListenerOptions } from './get_listener_options'; + +export function createServer(serverOptions: ServerOptions, listenerOptions: ListenerOptions) { + const server = new Server(serverOptions); + + server.listener.keepAliveTimeout = listenerOptions.keepaliveTimeout; + server.listener.setTimeout(listenerOptions.socketTimeout); + server.listener.on('timeout', (socket) => { + socket.destroy(); + }); + server.listener.on('clientError', (err, socket) => { + if (socket.writable) { + socket.end(Buffer.from('HTTP/1.1 400 Bad Request\r\n\r\n', 'ascii')); + } else { + socket.destroy(err); + } + }); + + return server; +} diff --git a/packages/kbn-server-http-tools/src/default_validation_error_handler.test.ts b/packages/kbn-server-http-tools/src/default_validation_error_handler.test.ts new file mode 100644 index 00000000000000..93b09ef13e0307 --- /dev/null +++ b/packages/kbn-server-http-tools/src/default_validation_error_handler.test.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 + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import Joi from 'joi'; +import { Request, ResponseToolkit } from '@hapi/hapi'; +import { + defaultValidationErrorHandler, + HapiValidationError, +} from './default_validation_error_handler'; + +const emptyOutput = { + statusCode: 400, + headers: {}, + payload: { + statusCode: 400, + error: '', + validation: { + source: '', + keys: [], + }, + }, +}; + +describe('defaultValidationErrorHandler', () => { + it('formats value validation errors correctly', () => { + expect.assertions(1); + const schema = Joi.array().items( + Joi.object({ + type: Joi.string().required(), + }).required() + ); + + const error = schema.validate([{}], { abortEarly: false }).error as HapiValidationError; + + // Emulate what Hapi v17 does by default + error.output = { ...emptyOutput }; + error.output.payload.validation.keys = ['0.type', '']; + + try { + defaultValidationErrorHandler({} as Request, {} as ResponseToolkit, error); + } catch (err) { + // Verify the empty string gets corrected to 'value' + expect(err.output.payload.validation.keys).toEqual(['0.type', 'value']); + } + }); +}); diff --git a/packages/kbn-server-http-tools/src/default_validation_error_handler.ts b/packages/kbn-server-http-tools/src/default_validation_error_handler.ts new file mode 100644 index 00000000000000..d2f4e993f3e4b2 --- /dev/null +++ b/packages/kbn-server-http-tools/src/default_validation_error_handler.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 + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { Lifecycle, Request, ResponseToolkit, Util } from '@hapi/hapi'; +import { ValidationError } from 'joi'; +import Hoek from '@hapi/hoek'; + +/** + * Hapi extends the ValidationError interface to add this output key with more data. + */ +export interface HapiValidationError extends ValidationError { + output: { + statusCode: number; + headers: Util.Dictionary; + payload: { + statusCode: number; + error: string; + message?: string; + validation: { + source: string; + keys: string[]; + }; + }; + }; +} + +/** + * Used to replicate Hapi v16 and below's validation responses. Should be used in the routes.validate.failAction key. + */ +export function defaultValidationErrorHandler( + request: Request, + h: ResponseToolkit, + err?: Error +): Lifecycle.ReturnValue { + // Newer versions of Joi don't format the key for missing params the same way. This shim + // provides backwards compatibility. Unfortunately, Joi doesn't export it's own Error class + // in JS so we have to rely on the `name` key before we can cast it. + // + // The Hapi code we're 'overwriting' can be found here: + // https://github.com/hapijs/hapi/blob/master/lib/validation.js#L102 + if (err && err.name === 'ValidationError' && err.hasOwnProperty('output')) { + const validationError: HapiValidationError = err as HapiValidationError; + const validationKeys: string[] = []; + + validationError.details.forEach((detail) => { + if (detail.path.length > 0) { + validationKeys.push(Hoek.escapeHtml(detail.path.join('.'))); + } else { + // If no path, use the value sigil to signal the entire value had an issue. + validationKeys.push('value'); + } + }); + + validationError.output.payload.validation.keys = validationKeys; + } + + throw err; +} diff --git a/packages/kbn-server-http-tools/src/get_listener_options.ts b/packages/kbn-server-http-tools/src/get_listener_options.ts new file mode 100644 index 00000000000000..00884312b599fb --- /dev/null +++ b/packages/kbn-server-http-tools/src/get_listener_options.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 + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { IHttpConfig } from './types'; + +export interface ListenerOptions { + keepaliveTimeout: number; + socketTimeout: number; +} + +export function getListenerOptions(config: IHttpConfig): ListenerOptions { + return { + keepaliveTimeout: config.keepaliveTimeout, + socketTimeout: config.socketTimeout, + }; +} diff --git a/packages/kbn-server-http-tools/src/get_request_id.test.ts b/packages/kbn-server-http-tools/src/get_request_id.test.ts new file mode 100644 index 00000000000000..1b098ed4842d30 --- /dev/null +++ b/packages/kbn-server-http-tools/src/get_request_id.test.ts @@ -0,0 +1,85 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { getRequestId } from './get_request_id'; + +jest.mock('uuid', () => ({ + v4: jest.fn().mockReturnValue('xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'), +})); + +describe('getRequestId', () => { + describe('when allowFromAnyIp is true', () => { + it('generates a UUID if no x-opaque-id header is present', () => { + const request = { + headers: {}, + raw: { req: { socket: { remoteAddress: '1.1.1.1' } } }, + } as any; + expect(getRequestId(request, { allowFromAnyIp: true, ipAllowlist: [] })).toEqual( + 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' + ); + }); + + it('uses x-opaque-id header value if present', () => { + const request = { + headers: { + 'x-opaque-id': 'id from header', + }, + raw: { req: { socket: { remoteAddress: '1.1.1.1' } } }, + } as any; + expect(getRequestId(request, { allowFromAnyIp: true, ipAllowlist: [] })).toEqual( + 'id from header' + ); + }); + }); + + describe('when allowFromAnyIp is false', () => { + describe('and ipAllowlist is empty', () => { + it('generates a UUID even if x-opaque-id header is present', () => { + const request = { + headers: { 'x-opaque-id': 'id from header' }, + raw: { req: { socket: { remoteAddress: '1.1.1.1' } } }, + } as any; + expect(getRequestId(request, { allowFromAnyIp: false, ipAllowlist: [] })).toEqual( + 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' + ); + }); + }); + + describe('and ipAllowlist is not empty', () => { + it('uses x-opaque-id header if request comes from trusted IP address', () => { + const request = { + headers: { 'x-opaque-id': 'id from header' }, + raw: { req: { socket: { remoteAddress: '1.1.1.1' } } }, + } as any; + expect(getRequestId(request, { allowFromAnyIp: false, ipAllowlist: ['1.1.1.1'] })).toEqual( + 'id from header' + ); + }); + + it('generates a UUID if request comes from untrusted IP address', () => { + const request = { + headers: { 'x-opaque-id': 'id from header' }, + raw: { req: { socket: { remoteAddress: '5.5.5.5' } } }, + } as any; + expect(getRequestId(request, { allowFromAnyIp: false, ipAllowlist: ['1.1.1.1'] })).toEqual( + 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' + ); + }); + + it('generates UUID if request comes from trusted IP address but no x-opaque-id header is present', () => { + const request = { + headers: {}, + raw: { req: { socket: { remoteAddress: '1.1.1.1' } } }, + } as any; + expect(getRequestId(request, { allowFromAnyIp: false, ipAllowlist: ['1.1.1.1'] })).toEqual( + 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' + ); + }); + }); + }); +}); diff --git a/packages/kbn-server-http-tools/src/get_request_id.ts b/packages/kbn-server-http-tools/src/get_request_id.ts new file mode 100644 index 00000000000000..3af70ecc3a7a32 --- /dev/null +++ b/packages/kbn-server-http-tools/src/get_request_id.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 + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { Request } from '@hapi/hapi'; +import uuid from 'uuid'; + +export function getRequestId( + request: Request, + { allowFromAnyIp, ipAllowlist }: { allowFromAnyIp: boolean; ipAllowlist: string[] } +): string { + const remoteAddress = request.raw.req.socket?.remoteAddress; + return allowFromAnyIp || + // socket may be undefined in integration tests that connect via the http listener directly + (remoteAddress && ipAllowlist.includes(remoteAddress)) + ? request.headers['x-opaque-id'] ?? uuid.v4() + : uuid.v4(); +} diff --git a/packages/kbn-server-http-tools/src/get_server_options.test.ts b/packages/kbn-server-http-tools/src/get_server_options.test.ts new file mode 100644 index 00000000000000..fdcc749f4ae9a1 --- /dev/null +++ b/packages/kbn-server-http-tools/src/get_server_options.test.ts @@ -0,0 +1,122 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ByteSizeValue } from '@kbn/config-schema'; +import { getServerOptions } from './get_server_options'; +import { IHttpConfig } from './types'; + +jest.mock('fs', () => { + const original = jest.requireActual('fs'); + return { + // Hapi Inert patches native methods + ...original, + readFileSync: jest.fn(), + }; +}); + +const createConfig = (parts: Partial): IHttpConfig => ({ + host: 'localhost', + port: 5601, + socketTimeout: 120000, + keepaliveTimeout: 120000, + maxPayload: ByteSizeValue.parse('1048576b'), + ...parts, + cors: { + enabled: false, + allowCredentials: false, + allowOrigin: ['*'], + ...parts.cors, + }, + ssl: { + enabled: false, + ...parts.ssl, + }, +}); + +describe('getServerOptions', () => { + beforeEach(() => + jest.requireMock('fs').readFileSync.mockImplementation((path: string) => `content-${path}`) + ); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('properly configures TLS with default options', () => { + const httpConfig = createConfig({ + ssl: { + enabled: true, + key: 'some-key-path', + certificate: 'some-certificate-path', + }, + }); + + expect(getServerOptions(httpConfig).tls).toMatchInlineSnapshot(` + Object { + "ca": undefined, + "cert": "some-certificate-path", + "ciphers": undefined, + "honorCipherOrder": true, + "key": "some-key-path", + "passphrase": undefined, + "rejectUnauthorized": undefined, + "requestCert": undefined, + "secureOptions": undefined, + } + `); + }); + + it('properly configures TLS with client authentication', () => { + const httpConfig = createConfig({ + ssl: { + enabled: true, + key: 'some-key-path', + certificate: 'some-certificate-path', + certificateAuthorities: ['ca-1', 'ca-2'], + cipherSuites: ['suite-a', 'suite-b'], + keyPassphrase: 'passPhrase', + rejectUnauthorized: true, + requestCert: true, + getSecureOptions: () => 42, + }, + }); + + expect(getServerOptions(httpConfig).tls).toMatchInlineSnapshot(` + Object { + "ca": Array [ + "ca-1", + "ca-2", + ], + "cert": "some-certificate-path", + "ciphers": "suite-a:suite-b", + "honorCipherOrder": true, + "key": "some-key-path", + "passphrase": "passPhrase", + "rejectUnauthorized": true, + "requestCert": true, + "secureOptions": 42, + } + `); + }); + + it('properly configures CORS when cors enabled', () => { + const httpConfig = createConfig({ + cors: { + enabled: true, + allowCredentials: false, + allowOrigin: ['*'], + }, + }); + + expect(getServerOptions(httpConfig).routes?.cors).toEqual({ + credentials: false, + origin: ['*'], + headers: ['Accept', 'Authorization', 'Content-Type', 'If-None-Match', 'kbn-xsrf'], + }); + }); +}); diff --git a/packages/kbn-server-http-tools/src/get_server_options.ts b/packages/kbn-server-http-tools/src/get_server_options.ts new file mode 100644 index 00000000000000..ade90a0e0d3f5d --- /dev/null +++ b/packages/kbn-server-http-tools/src/get_server_options.ts @@ -0,0 +1,75 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { RouteOptionsCors, ServerOptions } from '@hapi/hapi'; +import { ServerOptions as TLSOptions } from 'https'; +import { defaultValidationErrorHandler } from './default_validation_error_handler'; +import { IHttpConfig } from './types'; + +const corsAllowedHeaders = ['Accept', 'Authorization', 'Content-Type', 'If-None-Match', 'kbn-xsrf']; + +/** + * Converts Kibana `HttpConfig` into `ServerOptions` that are accepted by the Hapi server. + */ +export function getServerOptions(config: IHttpConfig, { configureTLS = true } = {}) { + const cors: RouteOptionsCors | false = config.cors.enabled + ? { + credentials: config.cors.allowCredentials, + origin: config.cors.allowOrigin, + headers: corsAllowedHeaders, + } + : false; + const options: ServerOptions = { + host: config.host, + port: config.port, + routes: { + cache: { + privacy: 'private', + otherwise: 'private, no-cache, no-store, must-revalidate', + }, + cors, + payload: { + maxBytes: config.maxPayload.getValueInBytes(), + }, + validate: { + failAction: defaultValidationErrorHandler, + options: { + abortEarly: false, + }, + }, + }, + state: { + strictHeader: false, + isHttpOnly: true, + isSameSite: false, // necessary to allow using Kibana inside an iframe + }, + }; + + if (configureTLS && config.ssl.enabled) { + const ssl = config.ssl; + + // TODO: Hapi types have a typo in `tls` property type definition: `https.RequestOptions` is used instead of + // `https.ServerOptions`, and `honorCipherOrder` isn't presented in `https.RequestOptions`. + const tlsOptions: TLSOptions = { + ca: ssl.certificateAuthorities, + cert: ssl.certificate, + ciphers: config.ssl.cipherSuites?.join(':'), + // We use the server's cipher order rather than the client's to prevent the BEAST attack. + honorCipherOrder: true, + key: ssl.key, + passphrase: ssl.keyPassphrase, + secureOptions: ssl.getSecureOptions ? ssl.getSecureOptions() : undefined, + requestCert: ssl.requestCert, + rejectUnauthorized: ssl.rejectUnauthorized, + }; + + options.tls = tlsOptions; + } + + return options; +} diff --git a/packages/kbn-server-http-tools/src/index.ts b/packages/kbn-server-http-tools/src/index.ts new file mode 100644 index 00000000000000..bd1dffa0bb0cab --- /dev/null +++ b/packages/kbn-server-http-tools/src/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 + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export type { IHttpConfig, ISslConfig, ICorsConfig } from './types'; +export { createServer } from './create_server'; +export { defaultValidationErrorHandler } from './default_validation_error_handler'; +export { getListenerOptions } from './get_listener_options'; +export { getServerOptions } from './get_server_options'; +export { getRequestId } from './get_request_id'; +export { sslSchema, SslConfig } from './ssl'; diff --git a/src/core/server/legacy/cli_dev_mode.js b/packages/kbn-server-http-tools/src/ssl/index.ts similarity index 86% rename from src/core/server/legacy/cli_dev_mode.js rename to packages/kbn-server-http-tools/src/ssl/index.ts index 3c4bdb4149780a..cbc3f17f915efd 100644 --- a/src/core/server/legacy/cli_dev_mode.js +++ b/packages/kbn-server-http-tools/src/ssl/index.ts @@ -6,4 +6,4 @@ * Side Public License, v 1. */ -export { CliDevMode } from '../../../dev/cli_dev_mode'; +export { SslConfig, sslSchema } from './ssl_config'; diff --git a/src/core/server/http/ssl_config.test.mocks.ts b/packages/kbn-server-http-tools/src/ssl/ssl_config.test.mocks.ts similarity index 95% rename from src/core/server/http/ssl_config.test.mocks.ts rename to packages/kbn-server-http-tools/src/ssl/ssl_config.test.mocks.ts index 81dbcf55100f87..adc4adb76f8047 100644 --- a/src/core/server/http/ssl_config.test.mocks.ts +++ b/packages/kbn-server-http-tools/src/ssl/ssl_config.test.mocks.ts @@ -13,7 +13,7 @@ jest.mock('fs', () => { export const mockReadPkcs12Keystore = jest.fn(); export const mockReadPkcs12Truststore = jest.fn(); -jest.mock('../utils', () => ({ +jest.mock('@kbn/crypto', () => ({ readPkcs12Keystore: mockReadPkcs12Keystore, readPkcs12Truststore: mockReadPkcs12Truststore, })); diff --git a/src/core/server/http/ssl_config.test.ts b/packages/kbn-server-http-tools/src/ssl/ssl_config.test.ts similarity index 99% rename from src/core/server/http/ssl_config.test.ts rename to packages/kbn-server-http-tools/src/ssl/ssl_config.test.ts index bb6b1c7ff29f3e..112fcd8a449f75 100644 --- a/src/core/server/http/ssl_config.test.ts +++ b/packages/kbn-server-http-tools/src/ssl/ssl_config.test.ts @@ -34,7 +34,7 @@ describe('#SslConfig', () => { beforeEach(() => { const realFs = jest.requireActual('fs'); mockReadFileSync.mockImplementation((path: string) => realFs.readFileSync(path)); - const utils = jest.requireActual('../utils'); + const utils = jest.requireActual('@kbn/crypto'); mockReadPkcs12Keystore.mockImplementation((path: string, password?: string) => utils.readPkcs12Keystore(path, password) ); diff --git a/src/core/server/http/ssl_config.ts b/packages/kbn-server-http-tools/src/ssl/ssl_config.ts similarity index 93% rename from src/core/server/http/ssl_config.ts rename to packages/kbn-server-http-tools/src/ssl/ssl_config.ts index 917d416a775639..53d3616a09a75e 100644 --- a/src/core/server/http/ssl_config.ts +++ b/packages/kbn-server-http-tools/src/ssl/ssl_config.ts @@ -7,9 +7,9 @@ */ import { schema, TypeOf } from '@kbn/config-schema'; +import { readPkcs12Keystore, readPkcs12Truststore } from '@kbn/crypto'; import { constants as cryptoConstants } from 'crypto'; import { readFileSync } from 'fs'; -import { readPkcs12Keystore, readPkcs12Truststore } from '../utils'; const protocolMap = new Map([ ['TLSv1', cryptoConstants.SSL_OP_NO_TLSv1], @@ -81,14 +81,13 @@ type SslConfigType = TypeOf; export class SslConfig { public enabled: boolean; - public redirectHttpFromPort: number | undefined; - public key: string | undefined; - public certificate: string | undefined; - public certificateAuthorities: string[] | undefined; - public keyPassphrase: string | undefined; + public redirectHttpFromPort?: number; + public key?: string; + public certificate?: string; + public certificateAuthorities?: string[]; + public keyPassphrase?: string; public requestCert: boolean; public rejectUnauthorized: boolean; - public cipherSuites: string[]; public supportedProtocols: string[]; @@ -164,6 +163,4 @@ export class SslConfig { } } -const readFile = (file: string) => { - return readFileSync(file, 'utf8'); -}; +const readFile = (file: string) => readFileSync(file, 'utf8'); diff --git a/packages/kbn-server-http-tools/src/types.ts b/packages/kbn-server-http-tools/src/types.ts new file mode 100644 index 00000000000000..3cc117d542eeec --- /dev/null +++ b/packages/kbn-server-http-tools/src/types.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ByteSizeValue } from '@kbn/config-schema'; + +export interface IHttpConfig { + host: string; + port: number; + maxPayload: ByteSizeValue; + keepaliveTimeout: number; + socketTimeout: number; + cors: ICorsConfig; + ssl: ISslConfig; +} + +export interface ICorsConfig { + enabled: boolean; + allowCredentials: boolean; + allowOrigin: string[]; +} + +export interface ISslConfig { + enabled: boolean; + key?: string; + certificate?: string; + certificateAuthorities?: string[]; + cipherSuites?: string[]; + keyPassphrase?: string; + requestCert?: boolean; + rejectUnauthorized?: boolean; + getSecureOptions?: () => number; +} diff --git a/packages/kbn-server-http-tools/tsconfig.json b/packages/kbn-server-http-tools/tsconfig.json new file mode 100644 index 00000000000000..ec84b963aed700 --- /dev/null +++ b/packages/kbn-server-http-tools/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target", + "declaration": true, + "declarationMap": true + }, + "include": [ + "src/**/*" + ], + "dependencies": { + "@kbn/std": "link:../kbn-std" + } +} diff --git a/packages/kbn-utils/src/repo_root.ts b/packages/kbn-utils/src/repo_root.ts index 20a25023f41660..2c1617098fe20f 100644 --- a/packages/kbn-utils/src/repo_root.ts +++ b/packages/kbn-utils/src/repo_root.ts @@ -57,3 +57,5 @@ const { kibanaDir, kibanaPkgJson } = findKibanaPackageJson(); export const REPO_ROOT = kibanaDir; export const UPSTREAM_BRANCH = kibanaPkgJson.branch; + +export const fromRoot = (...paths: string[]) => Path.resolve(REPO_ROOT, ...paths); diff --git a/src/cli/serve/serve.js b/src/cli/serve/serve.js index 34b78bbd7e51e8..86b4ac53841f71 100644 --- a/src/cli/serve/serve.js +++ b/src/cli/serve/serve.js @@ -12,10 +12,8 @@ import { statSync } from 'fs'; import { resolve } from 'path'; import url from 'url'; -import { getConfigPath } from '@kbn/utils'; +import { getConfigPath, fromRoot } from '@kbn/utils'; import { IS_KIBANA_DISTRIBUTABLE } from '../../legacy/utils'; -import { fromRoot } from '../../core/server/utils'; -import { bootstrap } from '../../core/server'; import { readKeystore } from '../keystore/read_keystore'; function canRequire(path) { @@ -31,9 +29,21 @@ function canRequire(path) { } } -const DEV_MODE_PATH = resolve(__dirname, '../../dev/cli_dev_mode'); +const DEV_MODE_PATH = '@kbn/cli-dev-mode'; const DEV_MODE_SUPPORTED = canRequire(DEV_MODE_PATH); +const getBootstrapScript = (isDev) => { + if (DEV_MODE_SUPPORTED && isDev && process.env.isDevCliChild !== 'true') { + // need dynamic require to exclude it from production build + // eslint-disable-next-line import/no-dynamic-require + const { bootstrapDevMode } = require(DEV_MODE_PATH); + return bootstrapDevMode; + } else { + const { bootstrap } = require('../../core/server'); + return bootstrap; + } +}; + const pathCollector = function () { const paths = []; return function (path) { @@ -79,6 +89,7 @@ function applyConfigOverrides(rawConfig, opts, extraCliOptions) { throw new Error(`Can't use --ssl when "${path}" configuration is already defined.`); } } + ensureNotDefined('server.ssl.certificate'); ensureNotDefined('server.ssl.key'); ensureNotDefined('server.ssl.keystore.path'); @@ -210,31 +221,40 @@ export default function (program) { } const unknownOptions = this.getUnknownOptions(); - await bootstrap({ - configs: [].concat(opts.config || []), - cliArgs: { - dev: !!opts.dev, - envName: unknownOptions.env ? unknownOptions.env.name : undefined, - // no longer supported - quiet: !!opts.quiet, - silent: !!opts.silent, - watch: !!opts.watch, - runExamples: !!opts.runExamples, - // 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, - disableOptimizer: !opts.optimizer, - oss: !!opts.oss, - cache: !!opts.cache, - dist: !!opts.dist, - }, - features: { - isCliDevModeSupported: DEV_MODE_SUPPORTED, - }, + const configs = [].concat(opts.config || []); + const cliArgs = { + dev: !!opts.dev, + envName: unknownOptions.env ? unknownOptions.env.name : undefined, + // no longer supported + quiet: !!opts.quiet, + silent: !!opts.silent, + watch: !!opts.watch, + runExamples: !!opts.runExamples, + // 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, + disableOptimizer: !opts.optimizer, + oss: !!opts.oss, + cache: !!opts.cache, + dist: !!opts.dist, + }; + + // In development mode, the main process uses the @kbn/dev-cli-mode + // bootstrap script instead of core's. The DevCliMode instance + // is in charge of starting up the optimizer, and spawning another + // `/script/kibana` process with the `isDevCliChild` varenv set to true. + // This variable is then used to identify that we're the 'real' + // Kibana server process, and will be using core's bootstrap script + // to effectively start Kibana. + const bootstrapScript = getBootstrapScript(cliArgs.dev); + + await bootstrapScript({ + configs, + cliArgs, applyConfigOverrides: (rawConfig) => applyConfigOverrides(rawConfig, opts, unknownOptions), }); }); diff --git a/src/core/server/bootstrap.ts b/src/core/server/bootstrap.ts index 42f6d9aedf1d69..4a07e0c010685a 100644 --- a/src/core/server/bootstrap.ts +++ b/src/core/server/bootstrap.ts @@ -11,18 +11,10 @@ import { CliArgs, Env, RawConfigService } from './config'; import { Root } from './root'; import { CriticalError } from './errors'; -interface KibanaFeatures { - // Indicates whether we can run Kibana in dev mode in which Kibana is run as - // a child process together with optimizer "worker" processes that are - // orchestrated by a parent process (dev mode only feature). - isCliDevModeSupported: boolean; -} - interface BootstrapArgs { configs: string[]; cliArgs: CliArgs; applyConfigOverrides: (config: Record) => Record; - features: KibanaFeatures; } /** @@ -30,12 +22,7 @@ interface BootstrapArgs { * @internal * @param param0 - options */ -export async function bootstrap({ - configs, - cliArgs, - applyConfigOverrides, - features, -}: BootstrapArgs) { +export async function bootstrap({ configs, cliArgs, applyConfigOverrides }: BootstrapArgs) { if (cliArgs.optimize) { // --optimize is deprecated and does nothing now, avoid starting up and just shutdown return; @@ -52,7 +39,6 @@ export async function bootstrap({ const env = Env.createDefault(REPO_ROOT, { configs, cliArgs, - isDevCliParent: cliArgs.dev && features.isCliDevModeSupported && !process.env.isDevCliChild, }); const rawConfigService = new RawConfigService(env.configs, applyConfigOverrides); diff --git a/src/core/server/dev/dev_config.ts b/src/core/server/dev/dev_config.ts index 3a303a61c85637..2fec778d857137 100644 --- a/src/core/server/dev/dev_config.ts +++ b/src/core/server/dev/dev_config.ts @@ -6,26 +6,11 @@ * Side Public License, v 1. */ -import { schema, TypeOf } from '@kbn/config-schema'; +import { schema } from '@kbn/config-schema'; export const config = { path: 'dev', - schema: schema.object({ - basePathProxyTarget: schema.number({ - defaultValue: 5603, - }), - }), + // dev configuration is validated by the dev cli. + // we only need to register the `dev` schema to avoid failing core's config validation + schema: schema.object({}, { unknowns: 'ignore' }), }; - -export type DevConfigType = TypeOf; - -export class DevConfig { - public basePathProxyTargetPort: number; - - /** - * @internal - */ - constructor(rawConfig: DevConfigType) { - this.basePathProxyTargetPort = rawConfig.basePathProxyTarget; - } -} diff --git a/src/core/server/dev/index.ts b/src/core/server/dev/index.ts index 6e0fd343d2ec84..70257d2a5e6c51 100644 --- a/src/core/server/dev/index.ts +++ b/src/core/server/dev/index.ts @@ -6,5 +6,4 @@ * Side Public License, v 1. */ -export { config, DevConfig } from './dev_config'; -export type { DevConfigType } from './dev_config'; +export { config } from './dev_config'; diff --git a/src/core/server/elasticsearch/elasticsearch_config.test.mocks.ts b/src/core/server/elasticsearch/elasticsearch_config.test.mocks.ts index 32602849d2e450..63b2233b06a967 100644 --- a/src/core/server/elasticsearch/elasticsearch_config.test.mocks.ts +++ b/src/core/server/elasticsearch/elasticsearch_config.test.mocks.ts @@ -11,7 +11,7 @@ jest.mock('fs', () => ({ readFileSync: mockReadFileSync })); export const mockReadPkcs12Keystore = jest.fn(); export const mockReadPkcs12Truststore = jest.fn(); -jest.mock('../utils', () => ({ +jest.mock('@kbn/crypto', () => ({ readPkcs12Keystore: mockReadPkcs12Keystore, readPkcs12Truststore: mockReadPkcs12Truststore, })); diff --git a/src/core/server/elasticsearch/elasticsearch_config.test.ts b/src/core/server/elasticsearch/elasticsearch_config.test.ts index d3f9693bab229d..4b6cf220ccd525 100644 --- a/src/core/server/elasticsearch/elasticsearch_config.test.ts +++ b/src/core/server/elasticsearch/elasticsearch_config.test.ts @@ -244,12 +244,12 @@ describe('throws when config is invalid', () => { beforeAll(() => { const realFs = jest.requireActual('fs'); mockReadFileSync.mockImplementation((path: string) => realFs.readFileSync(path)); - const utils = jest.requireActual('../utils'); + const crypto = jest.requireActual('@kbn/crypto'); mockReadPkcs12Keystore.mockImplementation((path: string, password?: string) => - utils.readPkcs12Keystore(path, password) + crypto.readPkcs12Keystore(path, password) ); mockReadPkcs12Truststore.mockImplementation((path: string, password?: string) => - utils.readPkcs12Truststore(path, password) + crypto.readPkcs12Truststore(path, password) ); }); diff --git a/src/core/server/elasticsearch/elasticsearch_config.ts b/src/core/server/elasticsearch/elasticsearch_config.ts index 879002a6ece51e..d3432344f5a739 100644 --- a/src/core/server/elasticsearch/elasticsearch_config.ts +++ b/src/core/server/elasticsearch/elasticsearch_config.ts @@ -7,10 +7,10 @@ */ import { schema, TypeOf } from '@kbn/config-schema'; +import { readPkcs12Keystore, readPkcs12Truststore } from '@kbn/crypto'; import { Duration } from 'moment'; import { readFileSync } from 'fs'; import { ConfigDeprecationProvider } from 'src/core/server'; -import { readPkcs12Keystore, readPkcs12Truststore } from '../utils'; import { ServiceConfigDescriptor } from '../internal_types'; import { getReservedHeaders } from './default_headers'; diff --git a/src/core/server/external_url/external_url_config.ts b/src/core/server/external_url/external_url_config.ts index 7e4afbfbfea05a..da4e8199dc6230 100644 --- a/src/core/server/external_url/external_url_config.ts +++ b/src/core/server/external_url/external_url_config.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { createSHA256Hash } from '../utils'; +import { createSHA256Hash } from '@kbn/crypto'; import { config } from './config'; const DEFAULT_CONFIG = Object.freeze(config.schema.validate({})); diff --git a/src/core/server/http/base_path_proxy_server.test.ts b/src/core/server/http/base_path_proxy_server.test.ts deleted file mode 100644 index 80c03a2af9031b..00000000000000 --- a/src/core/server/http/base_path_proxy_server.test.ts +++ /dev/null @@ -1,1021 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { BasePathProxyServer, BasePathProxyServerOptions } from './base_path_proxy_server'; -import { loggingSystemMock } from '../logging/logging_system.mock'; -import { DevConfig } from '../dev/dev_config'; -import { EMPTY } from 'rxjs'; -import { HttpConfig } from './http_config'; -import { ByteSizeValue, schema } from '@kbn/config-schema'; -import { - KibanaRequest, - KibanaResponseFactory, - Router, - RouteValidationFunction, - RouteValidationResultFactory, -} from './router'; -import { HttpServer } from './http_server'; -import supertest from 'supertest'; -import { RequestHandlerContext } from 'kibana/server'; -import { readFileSync } from 'fs'; -import { KBN_CERT_PATH, KBN_KEY_PATH } from '@kbn/dev-utils'; -import { omit } from 'lodash'; -import { Readable } from 'stream'; - -/** - * Most of these tests are inspired by: - * src/core/server/http/http_server.test.ts - * and copied for completeness from that file. The modifications are that these tests use the developer proxy. - */ -describe('BasePathProxyServer', () => { - let server: HttpServer; - let proxyServer: BasePathProxyServer; - let config: HttpConfig; - let configWithSSL: HttpConfig; - let basePath: string; - let certificate: string; - let key: string; - let proxySupertest: supertest.SuperTest; - const logger = loggingSystemMock.createLogger(); - const enhanceWithContext = (fn: (...args: any[]) => any) => fn.bind(null, {}); - - beforeAll(() => { - certificate = readFileSync(KBN_CERT_PATH, 'utf8'); - key = readFileSync(KBN_KEY_PATH, 'utf8'); - }); - - beforeEach(async () => { - // setup the server but don't start it until each individual test so that routes can be dynamically configured per unit test. - server = new HttpServer(logger, 'tests'); - config = ({ - name: 'kibana', - host: '127.0.0.1', - port: 10012, - compression: { enabled: true }, - requestId: { - allowFromAnyIp: true, - ipAllowlist: [], - }, - autoListen: true, - keepaliveTimeout: 1000, - socketTimeout: 1000, - cors: { - enabled: false, - allowCredentials: false, - allowOrigin: [], - }, - ssl: { enabled: false }, - customResponseHeaders: {}, - maxPayload: new ByteSizeValue(1024), - rewriteBasePath: true, - } as unknown) as HttpConfig; - - configWithSSL = { - ...config, - ssl: { - enabled: true, - certificate, - cipherSuites: ['TLS_AES_256_GCM_SHA384'], - getSecureOptions: () => 0, - key, - redirectHttpFromPort: config.port + 1, - }, - } as HttpConfig; - - // setup and start the proxy server - const proxyConfig: HttpConfig = { ...config, port: 10013 }; - const devConfig = new DevConfig({ basePathProxyTarget: config.port }); - proxyServer = new BasePathProxyServer(logger, proxyConfig, devConfig); - const options: Readonly = { - shouldRedirectFromOldBasePath: () => true, - delayUntil: () => EMPTY, - }; - await proxyServer.start(options); - - // set the base path or throw if for some unknown reason it is not setup - if (proxyServer.basePath == null) { - throw new Error('Invalid null base path, all tests will fail'); - } else { - basePath = proxyServer.basePath; - } - proxySupertest = supertest(`http://127.0.0.1:${proxyConfig.port}`); - }); - - afterEach(async () => { - await server.stop(); - await proxyServer.stop(); - jest.clearAllMocks(); - }); - - test('root URL will return a 302 redirect', async () => { - await proxySupertest.get('/').expect(302); - }); - - test('root URL will return a redirect location with exactly 3 characters that are a-z', async () => { - const res = await proxySupertest.get('/'); - const location = res.header.location; - expect(location).toMatch(/[a-z]{3}/); - }); - - test('valid params', async () => { - const router = new Router(`${basePath}/foo`, logger, enhanceWithContext); - router.get( - { - path: '/{test}', - validate: { - params: schema.object({ - test: schema.string(), - }), - }, - }, - (_, req, res) => { - return res.ok({ body: req.params.test }); - } - ); - const { registerRouter } = await server.setup(config); - registerRouter(router); - await server.start(); - - await proxySupertest - .get(`${basePath}/foo/some-string`) - .expect(200) - .then((res) => { - expect(res.text).toBe('some-string'); - }); - }); - - test('invalid params', async () => { - const router = new Router(`${basePath}/foo`, logger, enhanceWithContext); - - router.get( - { - path: '/{test}', - validate: { - params: schema.object({ - test: schema.number(), - }), - }, - }, - (_, req, res) => { - return res.ok({ body: String(req.params.test) }); - } - ); - - const { registerRouter } = await server.setup(config); - registerRouter(router); - - await server.start(); - - await proxySupertest - .get(`${basePath}/foo/some-string`) - .expect(400) - .then((res) => { - expect(res.body).toEqual({ - error: 'Bad Request', - statusCode: 400, - message: '[request params.test]: expected value of type [number] but got [string]', - }); - }); - }); - - test('valid query', async () => { - const router = new Router(`${basePath}/foo`, logger, enhanceWithContext); - - router.get( - { - path: '/', - validate: { - query: schema.object({ - bar: schema.string(), - quux: schema.number(), - }), - }, - }, - (_, req, res) => { - return res.ok({ body: req.query }); - } - ); - - const { registerRouter } = await server.setup(config); - registerRouter(router); - - await server.start(); - - await proxySupertest - .get(`${basePath}/foo/?bar=test&quux=123`) - .expect(200) - .then((res) => { - expect(res.body).toEqual({ bar: 'test', quux: 123 }); - }); - }); - - test('invalid query', async () => { - const router = new Router(`${basePath}/foo`, logger, enhanceWithContext); - - router.get( - { - path: '/', - validate: { - query: schema.object({ - bar: schema.number(), - }), - }, - }, - (_, req, res) => { - return res.ok({ body: req.query }); - } - ); - - const { registerRouter } = await server.setup(config); - registerRouter(router); - - await server.start(); - - await proxySupertest - .get(`${basePath}/foo/?bar=test`) - .expect(400) - .then((res) => { - expect(res.body).toEqual({ - error: 'Bad Request', - statusCode: 400, - message: '[request query.bar]: expected value of type [number] but got [string]', - }); - }); - }); - - test('valid body', async () => { - const router = new Router(`${basePath}/foo`, logger, enhanceWithContext); - - router.post( - { - path: '/', - validate: { - body: schema.object({ - bar: schema.string(), - baz: schema.number(), - }), - }, - }, - (_, req, res) => { - return res.ok({ body: req.body }); - } - ); - - const { registerRouter } = await server.setup(config); - registerRouter(router); - - await server.start(); - - await proxySupertest - .post(`${basePath}/foo/`) - .send({ - bar: 'test', - baz: 123, - }) - .expect(200) - .then((res) => { - expect(res.body).toEqual({ bar: 'test', baz: 123 }); - }); - }); - - test('valid body with validate function', async () => { - const router = new Router(`${basePath}/foo`, logger, enhanceWithContext); - - router.post( - { - path: '/', - validate: { - body: ({ bar, baz } = {}, { ok, badRequest }) => { - if (typeof bar === 'string' && typeof baz === 'number') { - return ok({ bar, baz }); - } else { - return badRequest('Wrong payload', ['body']); - } - }, - }, - }, - (_, req, res) => { - return res.ok({ body: req.body }); - } - ); - - const { registerRouter } = await server.setup(config); - registerRouter(router); - - await server.start(); - - await proxySupertest - .post(`${basePath}/foo/`) - .send({ - bar: 'test', - baz: 123, - }) - .expect(200) - .then((res) => { - expect(res.body).toEqual({ bar: 'test', baz: 123 }); - }); - }); - - test('not inline validation - specifying params', async () => { - const router = new Router(`${basePath}/foo`, logger, enhanceWithContext); - - const bodyValidation = ( - { bar, baz }: any = {}, - { ok, badRequest }: RouteValidationResultFactory - ) => { - if (typeof bar === 'string' && typeof baz === 'number') { - return ok({ bar, baz }); - } else { - return badRequest('Wrong payload', ['body']); - } - }; - - router.post( - { - path: '/', - validate: { - body: bodyValidation, - }, - }, - (_, req, res) => { - return res.ok({ body: req.body }); - } - ); - - const { registerRouter } = await server.setup(config); - registerRouter(router); - - await server.start(); - - await proxySupertest - .post(`${basePath}/foo/`) - .send({ - bar: 'test', - baz: 123, - }) - .expect(200) - .then((res) => { - expect(res.body).toEqual({ bar: 'test', baz: 123 }); - }); - }); - - test('not inline validation - specifying validation handler', async () => { - const router = new Router(`${basePath}/foo`, logger, enhanceWithContext); - - const bodyValidation: RouteValidationFunction<{ bar: string; baz: number }> = ( - { bar, baz } = {}, - { ok, badRequest } - ) => { - if (typeof bar === 'string' && typeof baz === 'number') { - return ok({ bar, baz }); - } else { - return badRequest('Wrong payload', ['body']); - } - }; - - router.post( - { - path: '/', - validate: { - body: bodyValidation, - }, - }, - (_, req, res) => { - return res.ok({ body: req.body }); - } - ); - - const { registerRouter } = await server.setup(config); - registerRouter(router); - - await server.start(); - - await proxySupertest - .post(`${basePath}/foo/`) - .send({ - bar: 'test', - baz: 123, - }) - .expect(200) - .then((res) => { - expect(res.body).toEqual({ bar: 'test', baz: 123 }); - }); - }); - - test('not inline handler - KibanaRequest', async () => { - const router = new Router(`${basePath}/foo`, logger, enhanceWithContext); - - const handler = ( - context: RequestHandlerContext, - req: KibanaRequest, - res: KibanaResponseFactory - ) => { - const body = { - bar: req.body.bar.toUpperCase(), - baz: req.body.baz.toString(), - }; - - return res.ok({ body }); - }; - - router.post( - { - path: '/', - validate: { - body: ({ bar, baz } = {}, { ok, badRequest }) => { - if (typeof bar === 'string' && typeof baz === 'number') { - return ok({ bar, baz }); - } else { - return badRequest('Wrong payload', ['body']); - } - }, - }, - }, - handler - ); - - const { registerRouter } = await server.setup(config); - registerRouter(router); - - await server.start(); - - await proxySupertest - .post(`${basePath}/foo/`) - .send({ - bar: 'test', - baz: 123, - }) - .expect(200) - .then((res) => { - expect(res.body).toEqual({ bar: 'TEST', baz: '123' }); - }); - }); - - test('invalid body', async () => { - const router = new Router(`${basePath}/foo`, logger, enhanceWithContext); - - router.post( - { - path: '/', - validate: { - body: schema.object({ - bar: schema.number(), - }), - }, - }, - (_, req, res) => { - return res.ok({ body: req.body }); - } - ); - - const { registerRouter } = await server.setup(config); - registerRouter(router); - - await server.start(); - - await proxySupertest - .post(`${basePath}/foo/`) - .send({ bar: 'test' }) - .expect(400) - .then((res) => { - expect(res.body).toEqual({ - error: 'Bad Request', - statusCode: 400, - message: '[request body.bar]: expected value of type [number] but got [string]', - }); - }); - }); - - test('handles putting', async () => { - const router = new Router(`${basePath}/foo`, logger, enhanceWithContext); - - router.put( - { - path: '/', - validate: { - body: schema.object({ - key: schema.string(), - }), - }, - }, - (_, req, res) => { - return res.ok({ body: req.body }); - } - ); - - const { registerRouter } = await server.setup(config); - registerRouter(router); - - await server.start(); - - await proxySupertest - .put(`${basePath}/foo/`) - .send({ key: 'new value' }) - .expect(200) - .then((res) => { - expect(res.body).toEqual({ key: 'new value' }); - }); - }); - - test('handles deleting', async () => { - const router = new Router(`${basePath}/foo`, logger, enhanceWithContext); - - router.delete( - { - path: '/{id}', - validate: { - params: schema.object({ - id: schema.number(), - }), - }, - }, - (_, req, res) => { - return res.ok({ body: { key: req.params.id } }); - } - ); - - const { registerRouter } = await server.setup(config); - registerRouter(router); - - await server.start(); - - await proxySupertest - .delete(`${basePath}/foo/3`) - .expect(200) - .then((res) => { - expect(res.body).toEqual({ key: 3 }); - }); - }); - - describe('with `basepath: /bar` and `rewriteBasePath: false`', () => { - let configWithBasePath: HttpConfig; - - beforeEach(async () => { - configWithBasePath = { - ...config, - basePath: '/bar', - rewriteBasePath: false, - } as HttpConfig; - - const router = new Router(`${basePath}/`, logger, enhanceWithContext); - router.get({ path: '/', validate: false }, (_, __, res) => res.ok({ body: 'value:/' })); - router.get({ path: '/foo', validate: false }, (_, __, res) => res.ok({ body: 'value:/foo' })); - - const { registerRouter } = await server.setup(configWithBasePath); - registerRouter(router); - - await server.start(); - }); - - test('/bar => 404', async () => { - await proxySupertest.get(`${basePath}/bar`).expect(404); - }); - - test('/bar/ => 404', async () => { - await proxySupertest.get(`${basePath}/bar/`).expect(404); - }); - - test('/bar/foo => 404', async () => { - await proxySupertest.get(`${basePath}/bar/foo`).expect(404); - }); - - test('/ => /', async () => { - await proxySupertest - .get(`${basePath}/`) - .expect(200) - .then((res) => { - expect(res.text).toBe('value:/'); - }); - }); - - test('/foo => /foo', async () => { - await proxySupertest - .get(`${basePath}/foo`) - .expect(200) - .then((res) => { - expect(res.text).toBe('value:/foo'); - }); - }); - }); - - test('with defined `redirectHttpFromPort`', async () => { - const router = new Router(`${basePath}/`, logger, enhanceWithContext); - router.get({ path: '/', validate: false }, (_, __, res) => res.ok({ body: 'value:/' })); - - const { registerRouter } = await server.setup(configWithSSL); - registerRouter(router); - - await server.start(); - }); - - test('allows attaching metadata to attach meta-data tag strings to a route', async () => { - const tags = ['my:tag']; - const { registerRouter } = await server.setup(config); - - const router = new Router(basePath, logger, enhanceWithContext); - router.get({ path: '/with-tags', validate: false, options: { tags } }, (_, req, res) => - res.ok({ body: { tags: req.route.options.tags } }) - ); - router.get({ path: '/without-tags', validate: false }, (_, req, res) => - res.ok({ body: { tags: req.route.options.tags } }) - ); - registerRouter(router); - - await server.start(); - await proxySupertest.get(`${basePath}/with-tags`).expect(200, { tags }); - - await proxySupertest.get(`${basePath}/without-tags`).expect(200, { tags: [] }); - }); - - describe('response headers', () => { - test('default headers', async () => { - const { registerRouter } = await server.setup(config); - - const router = new Router(basePath, logger, enhanceWithContext); - router.get({ path: '/', validate: false }, (_, req, res) => res.ok({ body: req.route })); - registerRouter(router); - - await server.start(); - const response = await proxySupertest.get(`${basePath}/`).expect(200); - - const restHeaders = omit(response.header, ['date', 'content-length']); - expect(restHeaders).toMatchInlineSnapshot(` - Object { - "accept-ranges": "bytes", - "cache-control": "private, no-cache, no-store, must-revalidate", - "connection": "close", - "content-type": "application/json; charset=utf-8", - } - `); - }); - }); - - test('exposes route details of incoming request to a route handler (POST + payload options)', async () => { - const { registerRouter } = await server.setup(config); - - const router = new Router(basePath, logger, enhanceWithContext); - router.post( - { - path: '/', - validate: { body: schema.object({ test: schema.number() }) }, - options: { body: { accepts: 'application/json' } }, - }, - (_, req, res) => res.ok({ body: req.route }) - ); - registerRouter(router); - - await server.start(); - await proxySupertest - .post(`${basePath}/`) - .send({ test: 1 }) - .expect(200, { - method: 'post', - path: `${basePath}/`, - options: { - authRequired: true, - xsrfRequired: true, - tags: [], - timeout: { - payload: 10000, - idleSocket: 1000, - }, - body: { - parse: true, // hapi populates the default - maxBytes: 1024, // hapi populates the default - accepts: ['application/json'], - output: 'data', - }, - }, - }); - }); - - test('should return a stream in the body', async () => { - const { registerRouter } = await server.setup(config); - - const router = new Router(basePath, logger, enhanceWithContext); - router.put( - { - path: '/', - validate: { body: schema.stream() }, - options: { body: { output: 'stream' } }, - }, - (_, req, res) => { - expect(req.body).toBeInstanceOf(Readable); - return res.ok({ body: req.route.options.body }); - } - ); - registerRouter(router); - - await server.start(); - await proxySupertest.put(`${basePath}/`).send({ test: 1 }).expect(200, { - parse: true, - maxBytes: 1024, // hapi populates the default - output: 'stream', - }); - }); - - describe('timeout options', () => { - describe('payload timeout', () => { - test('POST routes set the payload timeout', async () => { - const { registerRouter } = await server.setup(config); - - const router = new Router(basePath, logger, enhanceWithContext); - router.post( - { - path: '/', - validate: false, - options: { - timeout: { - payload: 300000, - }, - }, - }, - (_, req, res) => { - return res.ok({ - body: { - timeout: req.route.options.timeout, - }, - }); - } - ); - registerRouter(router); - await server.start(); - await proxySupertest - .post(`${basePath}/`) - .send({ test: 1 }) - .expect(200, { - timeout: { - payload: 300000, - idleSocket: 1000, // This is an extra option added by the proxy - }, - }); - }); - - test('DELETE routes set the payload timeout', async () => { - const { registerRouter } = await server.setup(config); - - const router = new Router(basePath, logger, enhanceWithContext); - router.delete( - { - path: '/', - validate: false, - options: { - timeout: { - payload: 300000, - }, - }, - }, - (context, req, res) => { - return res.ok({ - body: { - timeout: req.route.options.timeout, - }, - }); - } - ); - registerRouter(router); - await server.start(); - await proxySupertest.delete(`${basePath}/`).expect(200, { - timeout: { - payload: 300000, - idleSocket: 1000, // This is an extra option added by the proxy - }, - }); - }); - - test('PUT routes set the payload timeout and automatically adjusts the idle socket timeout', async () => { - const { registerRouter } = await server.setup(config); - - const router = new Router(basePath, logger, enhanceWithContext); - router.put( - { - path: '/', - validate: false, - options: { - timeout: { - payload: 300000, - }, - }, - }, - (_, req, res) => { - return res.ok({ - body: { - timeout: req.route.options.timeout, - }, - }); - } - ); - registerRouter(router); - await server.start(); - await proxySupertest.put(`${basePath}/`).expect(200, { - timeout: { - payload: 300000, - idleSocket: 1000, // This is an extra option added by the proxy - }, - }); - }); - - test('PATCH routes set the payload timeout and automatically adjusts the idle socket timeout', async () => { - const { registerRouter } = await server.setup(config); - - const router = new Router(basePath, logger, enhanceWithContext); - router.patch( - { - path: '/', - validate: false, - options: { - timeout: { - payload: 300000, - }, - }, - }, - (_, req, res) => { - return res.ok({ - body: { - timeout: req.route.options.timeout, - }, - }); - } - ); - registerRouter(router); - await server.start(); - await proxySupertest.patch(`${basePath}/`).expect(200, { - timeout: { - payload: 300000, - idleSocket: 1000, // This is an extra option added by the proxy - }, - }); - }); - }); - - describe('idleSocket timeout', () => { - test('uses server socket timeout when not specified in the route', async () => { - const { registerRouter } = await server.setup({ - ...config, - socketTimeout: 11000, - }); - - const router = new Router(basePath, logger, enhanceWithContext); - router.get( - { - path: '/', - validate: { body: schema.maybe(schema.any()) }, - }, - (_, req, res) => { - return res.ok({ - body: { - timeout: req.route.options.timeout, - }, - }); - } - ); - registerRouter(router); - - await server.start(); - await proxySupertest - .get(`${basePath}/`) - .send() - .expect(200, { - timeout: { - idleSocket: 11000, - }, - }); - }); - - test('sets the socket timeout when specified in the route', async () => { - const { registerRouter } = await server.setup({ - ...config, - socketTimeout: 11000, - }); - - const router = new Router(basePath, logger, enhanceWithContext); - router.get( - { - path: '/', - validate: { body: schema.maybe(schema.any()) }, - options: { timeout: { idleSocket: 12000 } }, - }, - (context, req, res) => { - return res.ok({ - body: { - timeout: req.route.options.timeout, - }, - }); - } - ); - registerRouter(router); - - await server.start(); - await proxySupertest - .get(`${basePath}/`) - .send() - .expect(200, { - timeout: { - idleSocket: 12000, - }, - }); - }); - - test('idleSocket timeout can be smaller than the payload timeout', async () => { - const { registerRouter } = await server.setup(config); - - const router = new Router(basePath, logger, enhanceWithContext); - router.post( - { - path: `${basePath}/`, - validate: { body: schema.any() }, - options: { - timeout: { - payload: 1000, - idleSocket: 10, - }, - }, - }, - (_, req, res) => { - return res.ok({ body: { timeout: req.route.options.timeout } }); - } - ); - - registerRouter(router); - - await server.start(); - }); - }); - }); - - describe('shouldRedirect', () => { - let proxyServerWithoutShouldRedirect: BasePathProxyServer; - let proxyWithoutShouldRedirectSupertest: supertest.SuperTest; - - beforeEach(async () => { - // setup and start a proxy server which does not use "shouldRedirectFromOldBasePath" - const proxyConfig: HttpConfig = { ...config, port: 10004 }; - const devConfig = new DevConfig({ basePathProxyTarget: config.port }); - proxyServerWithoutShouldRedirect = new BasePathProxyServer(logger, proxyConfig, devConfig); - const options: Readonly = { - shouldRedirectFromOldBasePath: () => false, // Return false to not redirect - delayUntil: () => EMPTY, - }; - await proxyServerWithoutShouldRedirect.start(options); - proxyWithoutShouldRedirectSupertest = supertest(`http://127.0.0.1:${proxyConfig.port}`); - }); - - afterEach(async () => { - await proxyServerWithoutShouldRedirect.stop(); - }); - - test('it will do a redirect if it detects what looks like a stale or previously used base path', async () => { - const fakeBasePath = basePath !== 'abc' ? 'abc' : 'efg'; - const res = await proxySupertest.get(`/${fakeBasePath}`).expect(302); - const location = res.header.location; - expect(location).toEqual(`${basePath}/`); - }); - - test('it will NOT do a redirect if it detects what looks like a stale or previously used base path if we intentionally turn it off', async () => { - const fakeBasePath = basePath !== 'abc' ? 'abc' : 'efg'; - await proxyWithoutShouldRedirectSupertest.get(`/${fakeBasePath}`).expect(404); - }); - - test('it will NOT redirect if it detects a larger path than 3 characters', async () => { - await proxySupertest.get('/abcde').expect(404); - }); - - test('it will NOT redirect if it is not a GET verb', async () => { - const fakeBasePath = basePath !== 'abc' ? 'abc' : 'efg'; - await proxySupertest.put(`/${fakeBasePath}`).expect(404); - }); - }); - - describe('constructor option for sending in a custom basePath', () => { - let proxyServerWithFooBasePath: BasePathProxyServer; - let proxyWithFooBasePath: supertest.SuperTest; - - beforeEach(async () => { - // setup and start a proxy server which uses a basePath of "foo" - const proxyConfig: HttpConfig = { ...config, port: 10004, basePath: '/foo' }; // <-- "foo" here in basePath - const devConfig = new DevConfig({ basePathProxyTarget: config.port }); - proxyServerWithFooBasePath = new BasePathProxyServer(logger, proxyConfig, devConfig); - const options: Readonly = { - shouldRedirectFromOldBasePath: () => true, - delayUntil: () => EMPTY, - }; - await proxyServerWithFooBasePath.start(options); - proxyWithFooBasePath = supertest(`http://127.0.0.1:${proxyConfig.port}`); - }); - - afterEach(async () => { - await proxyServerWithFooBasePath.stop(); - }); - - test('it will do a redirect to foo which is our passed in value for the configuration', async () => { - const res = await proxyWithFooBasePath.get('/bar').expect(302); - const location = res.header.location; - expect(location).toEqual('/foo/'); - }); - }); -}); diff --git a/src/core/server/http/http_config.ts b/src/core/server/http/http_config.ts index 2bbe9f3f96a559..356dad201ce95d 100644 --- a/src/core/server/http/http_config.ts +++ b/src/core/server/http/http_config.ts @@ -7,12 +7,12 @@ */ import { ByteSizeValue, schema, TypeOf } from '@kbn/config-schema'; +import { IHttpConfig, SslConfig, sslSchema } from '@kbn/server-http-tools'; import { hostname } from 'os'; import url from 'url'; import { CspConfigType, CspConfig, ICspConfig } from '../csp'; import { ExternalUrlConfig, IExternalUrlConfig } from '../external_url'; -import { SslConfig, sslSchema } from './ssl_config'; const validBasePathRegex = /^\/.*[^\/]$/; const uuidRegexp = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}$/i; @@ -156,7 +156,7 @@ export const config = { }; export type HttpConfigType = TypeOf; -export class HttpConfig { +export class HttpConfig implements IHttpConfig { public name: string; public autoListen: boolean; public host: string; diff --git a/src/core/server/http/http_server.test.ts b/src/core/server/http/http_server.test.ts index 54be7b35f68ad4..ccd14d4b99e112 100644 --- a/src/core/server/http/http_server.test.ts +++ b/src/core/server/http/http_server.test.ts @@ -1288,6 +1288,30 @@ test('should return a stream in the body', async () => { }); }); +test('closes sockets on timeout', async () => { + const { registerRouter, server: innerServer } = await server.setup({ + ...config, + socketTimeout: 1000, + }); + const router = new Router('', logger, enhanceWithContext); + + router.get({ path: '/a', validate: false }, async (context, req, res) => { + await new Promise((resolve) => setTimeout(resolve, 2000)); + return res.ok({}); + }); + router.get({ path: '/b', validate: false }, (context, req, res) => res.ok({})); + + registerRouter(router); + + registerRouter(router); + + await server.start(); + + expect(supertest(innerServer.listener).get('/a')).rejects.toThrow('socket hang up'); + + await supertest(innerServer.listener).get('/b').expect(200); +}); + describe('setup contract', () => { describe('#createSessionStorage', () => { test('creates session storage factory', async () => { diff --git a/src/core/server/http/http_server.ts b/src/core/server/http/http_server.ts index b0510bc414bf83..cd7d7ccc5aeffa 100644 --- a/src/core/server/http/http_server.ts +++ b/src/core/server/http/http_server.ts @@ -10,10 +10,15 @@ import { Server, Request } from '@hapi/hapi'; import HapiStaticFiles from '@hapi/inert'; import url from 'url'; import uuid from 'uuid'; +import { + createServer, + getListenerOptions, + getServerOptions, + getRequestId, +} from '@kbn/server-http-tools'; import { Logger, LoggerFactory } from '../logging'; import { HttpConfig } from './http_config'; -import { createServer, getListenerOptions, getServerOptions, getRequestId } from './http_tools'; import { adoptToHapiAuthFormat, AuthenticationHandler } from './lifecycle/auth'; import { adoptToHapiOnPreAuth, OnPreAuthHandler } from './lifecycle/on_pre_auth'; import { adoptToHapiOnPostAuthFormat, OnPostAuthHandler } from './lifecycle/on_post_auth'; diff --git a/src/core/server/http/http_service.test.ts b/src/core/server/http/http_service.test.ts index 9354c89b632929..83279e99bc4761 100644 --- a/src/core/server/http/http_service.test.ts +++ b/src/core/server/http/http_service.test.ts @@ -242,29 +242,6 @@ test('returns http server contract on setup', async () => { }); }); -test('does not start http server if process is dev cluster master', async () => { - const configService = createConfigService(); - const httpServer = { - isListening: () => false, - setup: jest.fn().mockReturnValue({}), - start: jest.fn(), - stop: noop, - }; - mockHttpServer.mockImplementation(() => httpServer); - - const service = new HttpService({ - coreId, - configService, - env: Env.createDefault(REPO_ROOT, getEnvOptions({ isDevCliParent: true })), - logger, - }); - - await service.setup(setupDeps); - await service.start(); - - expect(httpServer.start).not.toHaveBeenCalled(); -}); - test('does not start http server if configured with `autoListen:false`', async () => { const configService = createConfigService({ autoListen: false, diff --git a/src/core/server/http/http_service.ts b/src/core/server/http/http_service.ts index 87143e1160c6c3..5b90440f6ad701 100644 --- a/src/core/server/http/http_service.ts +++ b/src/core/server/http/http_service.ts @@ -153,15 +153,13 @@ export class HttpService } /** - * Indicates if http server has configured to start listening on a configured port. - * We shouldn't start http service in two cases: - * 1. If `server.autoListen` is explicitly set to `false`. - * 2. When the process is run as dev cluster master in which case cluster manager - * will fork a dedicated process where http service will be set up instead. + * Indicates if http server is configured to start listening on a configured port. + * (if `server.autoListen` is not explicitly set to `false`.) + * * @internal * */ private shouldListen(config: HttpConfig) { - return !this.coreContext.env.isDevCliParent && config.autoListen; + return config.autoListen; } public async stop() { diff --git a/src/core/server/http/http_tools.test.ts b/src/core/server/http/http_tools.test.ts deleted file mode 100644 index c2fa3816324fc4..00000000000000 --- a/src/core/server/http/http_tools.test.ts +++ /dev/null @@ -1,274 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -jest.mock('fs', () => { - const original = jest.requireActual('fs'); - return { - // Hapi Inert patches native methods - ...original, - readFileSync: jest.fn(), - }; -}); - -jest.mock('uuid', () => ({ - v4: jest.fn().mockReturnValue('xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'), -})); - -import supertest from 'supertest'; -import { Request, ResponseToolkit } from '@hapi/hapi'; -import Joi from 'joi'; - -import { - defaultValidationErrorHandler, - HapiValidationError, - getServerOptions, - getRequestId, -} from './http_tools'; -import { HttpServer } from './http_server'; -import { HttpConfig, config } from './http_config'; -import { Router } from './router'; -import { loggingSystemMock } from '../logging/logging_system.mock'; -import { ByteSizeValue } from '@kbn/config-schema'; - -const emptyOutput = { - statusCode: 400, - headers: {}, - payload: { - statusCode: 400, - error: '', - validation: { - source: '', - keys: [], - }, - }, -}; - -afterEach(() => jest.clearAllMocks()); - -describe('defaultValidationErrorHandler', () => { - it('formats value validation errors correctly', () => { - expect.assertions(1); - const schema = Joi.array().items( - Joi.object({ - type: Joi.string().required(), - }).required() - ); - - const error = schema.validate([{}], { abortEarly: false }).error as HapiValidationError; - - // Emulate what Hapi v17 does by default - error.output = { ...emptyOutput }; - error.output.payload.validation.keys = ['0.type', '']; - - try { - defaultValidationErrorHandler({} as Request, {} as ResponseToolkit, error); - } catch (err) { - // Verify the empty string gets corrected to 'value' - expect(err.output.payload.validation.keys).toEqual(['0.type', 'value']); - } - }); -}); - -describe('timeouts', () => { - const logger = loggingSystemMock.create(); - const server = new HttpServer(logger, 'foo'); - const enhanceWithContext = (fn: (...args: any[]) => any) => fn.bind(null, {}); - - test('closes sockets on timeout', async () => { - const router = new Router('', logger.get(), enhanceWithContext); - router.get({ path: '/a', validate: false }, async (context, req, res) => { - await new Promise((resolve) => setTimeout(resolve, 2000)); - return res.ok({}); - }); - router.get({ path: '/b', validate: false }, (context, req, res) => res.ok({})); - const { registerRouter, server: innerServer } = await server.setup({ - socketTimeout: 1000, - host: '127.0.0.1', - maxPayload: new ByteSizeValue(1024), - ssl: {}, - cors: { - enabled: false, - }, - compression: { enabled: true }, - requestId: { - allowFromAnyIp: true, - ipAllowlist: [], - }, - } as any); - registerRouter(router); - - await server.start(); - - expect(supertest(innerServer.listener).get('/a')).rejects.toThrow('socket hang up'); - - await supertest(innerServer.listener).get('/b').expect(200); - }); - - afterAll(async () => { - await server.stop(); - }); -}); - -describe('getServerOptions', () => { - beforeEach(() => - jest.requireMock('fs').readFileSync.mockImplementation((path: string) => `content-${path}`) - ); - - it('properly configures TLS with default options', () => { - const httpConfig = new HttpConfig( - config.schema.validate({ - ssl: { - enabled: true, - key: 'some-key-path', - certificate: 'some-certificate-path', - }, - }), - {} as any, - {} as any - ); - - expect(getServerOptions(httpConfig).tls).toMatchInlineSnapshot(` - Object { - "ca": undefined, - "cert": "content-some-certificate-path", - "ciphers": "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:DHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA256:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!SRP:!CAMELLIA", - "honorCipherOrder": true, - "key": "content-some-key-path", - "passphrase": undefined, - "rejectUnauthorized": false, - "requestCert": false, - "secureOptions": 67108864, - } - `); - }); - - it('properly configures TLS with client authentication', () => { - const httpConfig = new HttpConfig( - config.schema.validate({ - ssl: { - enabled: true, - key: 'some-key-path', - certificate: 'some-certificate-path', - certificateAuthorities: ['ca-1', 'ca-2'], - clientAuthentication: 'required', - }, - }), - {} as any, - {} as any - ); - - expect(getServerOptions(httpConfig).tls).toMatchInlineSnapshot(` - Object { - "ca": Array [ - "content-ca-1", - "content-ca-2", - ], - "cert": "content-some-certificate-path", - "ciphers": "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:DHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA256:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!SRP:!CAMELLIA", - "honorCipherOrder": true, - "key": "content-some-key-path", - "passphrase": undefined, - "rejectUnauthorized": true, - "requestCert": true, - "secureOptions": 67108864, - } - `); - }); - - it('properly configures CORS when cors enabled', () => { - const httpConfig = new HttpConfig( - config.schema.validate({ - cors: { - enabled: true, - allowCredentials: false, - allowOrigin: ['*'], - }, - }), - {} as any, - {} as any - ); - - expect(getServerOptions(httpConfig).routes?.cors).toEqual({ - credentials: false, - origin: ['*'], - headers: ['Accept', 'Authorization', 'Content-Type', 'If-None-Match', 'kbn-xsrf'], - }); - }); -}); - -describe('getRequestId', () => { - describe('when allowFromAnyIp is true', () => { - it('generates a UUID if no x-opaque-id header is present', () => { - const request = { - headers: {}, - raw: { req: { socket: { remoteAddress: '1.1.1.1' } } }, - } as any; - expect(getRequestId(request, { allowFromAnyIp: true, ipAllowlist: [] })).toEqual( - 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' - ); - }); - - it('uses x-opaque-id header value if present', () => { - const request = { - headers: { - 'x-opaque-id': 'id from header', - raw: { req: { socket: { remoteAddress: '1.1.1.1' } } }, - }, - } as any; - expect(getRequestId(request, { allowFromAnyIp: true, ipAllowlist: [] })).toEqual( - 'id from header' - ); - }); - }); - - describe('when allowFromAnyIp is false', () => { - describe('and ipAllowlist is empty', () => { - it('generates a UUID even if x-opaque-id header is present', () => { - const request = { - headers: { 'x-opaque-id': 'id from header' }, - raw: { req: { socket: { remoteAddress: '1.1.1.1' } } }, - } as any; - expect(getRequestId(request, { allowFromAnyIp: false, ipAllowlist: [] })).toEqual( - 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' - ); - }); - }); - - describe('and ipAllowlist is not empty', () => { - it('uses x-opaque-id header if request comes from trusted IP address', () => { - const request = { - headers: { 'x-opaque-id': 'id from header' }, - raw: { req: { socket: { remoteAddress: '1.1.1.1' } } }, - } as any; - expect(getRequestId(request, { allowFromAnyIp: false, ipAllowlist: ['1.1.1.1'] })).toEqual( - 'id from header' - ); - }); - - it('generates a UUID if request comes from untrusted IP address', () => { - const request = { - headers: { 'x-opaque-id': 'id from header' }, - raw: { req: { socket: { remoteAddress: '5.5.5.5' } } }, - } as any; - expect(getRequestId(request, { allowFromAnyIp: false, ipAllowlist: ['1.1.1.1'] })).toEqual( - 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' - ); - }); - - it('generates UUID if request comes from trusted IP address but no x-opaque-id header is present', () => { - const request = { - headers: {}, - raw: { req: { socket: { remoteAddress: '1.1.1.1' } } }, - } as any; - expect(getRequestId(request, { allowFromAnyIp: false, ipAllowlist: ['1.1.1.1'] })).toEqual( - 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' - ); - }); - }); - }); -}); diff --git a/src/core/server/http/http_tools.ts b/src/core/server/http/http_tools.ts deleted file mode 100644 index e909b454feae2c..00000000000000 --- a/src/core/server/http/http_tools.ts +++ /dev/null @@ -1,186 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { Server } from '@hapi/hapi'; -import type { - Lifecycle, - Request, - ResponseToolkit, - RouteOptionsCors, - ServerOptions, - Util, -} from '@hapi/hapi'; -import Hoek from '@hapi/hoek'; -import type { ServerOptions as TLSOptions } from 'https'; -import type { ValidationError } from 'joi'; -import uuid from 'uuid'; -import { ensureNoUnsafeProperties } from '@kbn/std'; -import { HttpConfig } from './http_config'; - -const corsAllowedHeaders = ['Accept', 'Authorization', 'Content-Type', 'If-None-Match', 'kbn-xsrf']; -/** - * Converts Kibana `HttpConfig` into `ServerOptions` that are accepted by the Hapi server. - */ -export function getServerOptions(config: HttpConfig, { configureTLS = true } = {}) { - const cors: RouteOptionsCors | false = config.cors.enabled - ? { - credentials: config.cors.allowCredentials, - origin: config.cors.allowOrigin, - headers: corsAllowedHeaders, - } - : false; - // Note that all connection options configured here should be exactly the same - // as in the legacy platform server (see `src/legacy/server/http/index`). Any change - // SHOULD BE applied in both places. The only exception is TLS-specific options, - // that are configured only here. - const options: ServerOptions = { - host: config.host, - port: config.port, - routes: { - cache: { - privacy: 'private', - otherwise: 'private, no-cache, no-store, must-revalidate', - }, - cors, - payload: { - maxBytes: config.maxPayload.getValueInBytes(), - }, - validate: { - failAction: defaultValidationErrorHandler, - options: { - abortEarly: false, - }, - // TODO: This payload validation can be removed once the legacy platform is completely removed. - // This is a default payload validation which applies to all LP routes which do not specify their own - // `validate.payload` handler, in order to reduce the likelyhood of prototype pollution vulnerabilities. - // (All NP routes are already required to specify their own validation in order to access the payload) - payload: (value) => Promise.resolve(ensureNoUnsafeProperties(value)), - }, - }, - state: { - strictHeader: false, - isHttpOnly: true, - isSameSite: false, // necessary to allow using Kibana inside an iframe - }, - }; - - if (configureTLS && config.ssl.enabled) { - const ssl = config.ssl; - - // TODO: Hapi types have a typo in `tls` property type definition: `https.RequestOptions` is used instead of - // `https.ServerOptions`, and `honorCipherOrder` isn't presented in `https.RequestOptions`. - const tlsOptions: TLSOptions = { - ca: ssl.certificateAuthorities, - cert: ssl.certificate, - ciphers: config.ssl.cipherSuites.join(':'), - // We use the server's cipher order rather than the client's to prevent the BEAST attack. - honorCipherOrder: true, - key: ssl.key, - passphrase: ssl.keyPassphrase, - secureOptions: ssl.getSecureOptions(), - requestCert: ssl.requestCert, - rejectUnauthorized: ssl.rejectUnauthorized, - }; - - options.tls = tlsOptions; - } - - return options; -} - -export function getListenerOptions(config: HttpConfig) { - return { - keepaliveTimeout: config.keepaliveTimeout, - socketTimeout: config.socketTimeout, - }; -} - -interface ListenerOptions { - keepaliveTimeout: number; - socketTimeout: number; -} - -export function createServer(serverOptions: ServerOptions, listenerOptions: ListenerOptions) { - const server = new Server(serverOptions); - - server.listener.keepAliveTimeout = listenerOptions.keepaliveTimeout; - server.listener.setTimeout(listenerOptions.socketTimeout); - server.listener.on('timeout', (socket) => { - socket.destroy(); - }); - server.listener.on('clientError', (err, socket) => { - if (socket.writable) { - socket.end(Buffer.from('HTTP/1.1 400 Bad Request\r\n\r\n', 'ascii')); - } else { - socket.destroy(err); - } - }); - - return server; -} - -/** - * Hapi extends the ValidationError interface to add this output key with more data. - */ -export interface HapiValidationError extends ValidationError { - output: { - statusCode: number; - headers: Util.Dictionary; - payload: { - statusCode: number; - error: string; - message?: string; - validation: { - source: string; - keys: string[]; - }; - }; - }; -} - -/** - * Used to replicate Hapi v16 and below's validation responses. Should be used in the routes.validate.failAction key. - */ -export function defaultValidationErrorHandler( - request: Request, - h: ResponseToolkit, - err?: Error -): Lifecycle.ReturnValue { - // Newer versions of Joi don't format the key for missing params the same way. This shim - // provides backwards compatibility. Unfortunately, Joi doesn't export it's own Error class - // in JS so we have to rely on the `name` key before we can cast it. - // - // The Hapi code we're 'overwriting' can be found here: - // https://github.com/hapijs/hapi/blob/master/lib/validation.js#L102 - if (err && err.name === 'ValidationError' && err.hasOwnProperty('output')) { - const validationError: HapiValidationError = err as HapiValidationError; - const validationKeys: string[] = []; - - validationError.details.forEach((detail) => { - if (detail.path.length > 0) { - validationKeys.push(Hoek.escapeHtml(detail.path.join('.'))); - } else { - // If no path, use the value sigil to signal the entire value had an issue. - validationKeys.push('value'); - } - }); - - validationError.output.payload.validation.keys = validationKeys; - } - - throw err; -} - -export function getRequestId(request: Request, options: HttpConfig['requestId']): string { - return options.allowFromAnyIp || - // socket may be undefined in integration tests that connect via the http listener directly - (request.raw.req.socket?.remoteAddress && - options.ipAllowlist.includes(request.raw.req.socket.remoteAddress)) - ? request.headers['x-opaque-id'] ?? uuid.v4() - : uuid.v4(); -} diff --git a/src/core/server/http/https_redirect_server.ts b/src/core/server/http/https_redirect_server.ts index dd29a46d728e72..28909c0308c223 100644 --- a/src/core/server/http/https_redirect_server.ts +++ b/src/core/server/http/https_redirect_server.ts @@ -8,10 +8,10 @@ import { Request, ResponseToolkit, Server } from '@hapi/hapi'; import { format as formatUrl } from 'url'; +import { createServer, getListenerOptions, getServerOptions } from '@kbn/server-http-tools'; import { Logger } from '../logging'; import { HttpConfig } from './http_config'; -import { createServer, getListenerOptions, getServerOptions } from './http_tools'; export class HttpsRedirectServer { private server?: Server; diff --git a/src/core/server/http/index.ts b/src/core/server/http/index.ts index c35b7e2fcd0429..84fe5149c89c66 100644 --- a/src/core/server/http/index.ts +++ b/src/core/server/http/index.ts @@ -56,7 +56,6 @@ export type { DestructiveRouteMethod, SafeRouteMethod, } from './router'; -export { BasePathProxyServer } from './base_path_proxy_server'; export type { OnPreRoutingHandler, OnPreRoutingToolkit } from './lifecycle/on_pre_routing'; export type { AuthenticationHandler, diff --git a/src/core/server/http/integration_tests/router.test.ts b/src/core/server/http/integration_tests/router.test.ts index 03324dc6c722ff..5b297ab44f8bbe 100644 --- a/src/core/server/http/integration_tests/router.test.ts +++ b/src/core/server/http/integration_tests/router.test.ts @@ -11,14 +11,12 @@ import Boom from '@hapi/boom'; import supertest from 'supertest'; import { schema } from '@kbn/config-schema'; -import { HttpService } from '../http_service'; - import { contextServiceMock } from '../../context/context_service.mock'; import { loggingSystemMock } from '../../logging/logging_system.mock'; import { createHttpServer } from '../test_utils'; +import { HttpService } from '../http_service'; let server: HttpService; - let logger: ReturnType; const contextSetup = contextServiceMock.createSetupContract(); @@ -28,7 +26,6 @@ const setupDeps = { beforeEach(() => { logger = loggingSystemMock.create(); - server = createHttpServer({ logger }); }); diff --git a/src/core/server/legacy/legacy_service.test.ts b/src/core/server/legacy/legacy_service.test.ts index da6b521bfde9a7..db36bd73602c47 100644 --- a/src/core/server/legacy/legacy_service.test.ts +++ b/src/core/server/legacy/legacy_service.test.ts @@ -7,16 +7,12 @@ */ jest.mock('../../../legacy/server/kbn_server'); -jest.mock('./cli_dev_mode'); import { BehaviorSubject, throwError } from 'rxjs'; import { REPO_ROOT } from '@kbn/dev-utils'; -// @ts-expect-error js file to remove TS dependency on cli -import { CliDevMode as MockCliDevMode } from './cli_dev_mode'; import KbnServer from '../../../legacy/server/kbn_server'; import { Config, Env, ObjectToConfigAdapter } from '../config'; -import { BasePathProxyServer } from '../http'; import { DiscoveredPlugin } from '../plugins'; import { getEnvOptions, configServiceMock } from '../config/mocks'; @@ -228,7 +224,6 @@ describe('once LegacyService is set up with connection info', () => { ); expect(MockKbnServer).not.toHaveBeenCalled(); - expect(MockCliDevMode).not.toHaveBeenCalled(); }); test('reconfigures logging configuration if new config is received.', async () => { @@ -335,74 +330,6 @@ describe('once LegacyService is set up without connection info', () => { }); }); -describe('once LegacyService is set up in `devClusterMaster` mode', () => { - beforeEach(() => { - configService.atPath.mockImplementation((path) => { - return new BehaviorSubject( - path === 'dev' ? { basePathProxyTargetPort: 100500 } : { basePath: '/abc' } - ); - }); - }); - - test('creates CliDevMode without base path proxy.', async () => { - const devClusterLegacyService = new LegacyService({ - coreId, - env: Env.createDefault( - REPO_ROOT, - getEnvOptions({ - cliArgs: { silent: true, basePath: false }, - isDevCliParent: true, - }) - ), - logger, - configService: configService as any, - }); - - await devClusterLegacyService.setupLegacyConfig(); - await devClusterLegacyService.setup(setupDeps); - await devClusterLegacyService.start(startDeps); - - expect(MockCliDevMode.fromCoreServices).toHaveBeenCalledTimes(1); - expect(MockCliDevMode.fromCoreServices).toHaveBeenCalledWith( - expect.objectContaining({ silent: true, basePath: false }), - expect.objectContaining({ - get: expect.any(Function), - set: expect.any(Function), - }), - undefined - ); - }); - - test('creates CliDevMode with base path proxy.', async () => { - const devClusterLegacyService = new LegacyService({ - coreId, - env: Env.createDefault( - REPO_ROOT, - getEnvOptions({ - cliArgs: { quiet: true, basePath: true }, - isDevCliParent: true, - }) - ), - logger, - configService: configService as any, - }); - - await devClusterLegacyService.setupLegacyConfig(); - await devClusterLegacyService.setup(setupDeps); - await devClusterLegacyService.start(startDeps); - - expect(MockCliDevMode.fromCoreServices).toHaveBeenCalledTimes(1); - expect(MockCliDevMode.fromCoreServices).toHaveBeenCalledWith( - expect.objectContaining({ quiet: true, basePath: true }), - expect.objectContaining({ - get: expect.any(Function), - set: expect.any(Function), - }), - expect.any(BasePathProxyServer) - ); - }); -}); - describe('start', () => { test('Cannot start without setup phase', async () => { const legacyService = new LegacyService({ diff --git a/src/core/server/legacy/legacy_service.ts b/src/core/server/legacy/legacy_service.ts index 63b84e2461e71b..f7abe942d0009d 100644 --- a/src/core/server/legacy/legacy_service.ts +++ b/src/core/server/legacy/legacy_service.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { combineLatest, ConnectableObservable, EMPTY, Observable, Subscription } from 'rxjs'; +import { combineLatest, ConnectableObservable, Observable, Subscription } from 'rxjs'; import { first, map, publishReplay, tap } from 'rxjs/operators'; import type { PublicMethodsOf } from '@kbn/utility-types'; import { PathConfigType } from '@kbn/utils'; @@ -18,9 +18,7 @@ import { CoreService } from '../../types'; import { Config } from '../config'; import { CoreContext } from '../core_context'; import { CspConfigType, config as cspConfig } from '../csp'; -import { DevConfig, DevConfigType, config as devConfig } from '../dev'; import { - BasePathProxyServer, HttpConfig, HttpConfigType, config as httpConfig, @@ -64,7 +62,6 @@ export class LegacyService implements CoreService { /** Symbol to represent the legacy platform as a fake "plugin". Used by the ContextService */ public readonly legacyId = Symbol(); private readonly log: Logger; - private readonly devConfig$: Observable; private readonly httpConfig$: Observable; private kbnServer?: LegacyKbnServer; private configSubscription?: Subscription; @@ -77,9 +74,6 @@ export class LegacyService implements CoreService { const { logger, configService } = coreContext; this.log = logger.get('legacy-service'); - this.devConfig$ = configService - .atPath(devConfig.path) - .pipe(map((rawConfig) => new DevConfig(rawConfig))); this.httpConfig$ = combineLatest( configService.atPath(httpConfig.path), configService.atPath(cspConfig.path), @@ -142,17 +136,12 @@ export class LegacyService implements CoreService { this.log.debug('starting legacy service'); - // Receive initial config and create kbnServer/ClusterManager. - if (this.coreContext.env.isDevCliParent) { - await this.setupCliDevMode(this.legacyRawConfig!); - } else { - this.kbnServer = await this.createKbnServer( - this.settings!, - this.legacyRawConfig!, - setupDeps, - startDeps - ); - } + this.kbnServer = await this.createKbnServer( + this.settings!, + this.legacyRawConfig!, + setupDeps, + startDeps + ); } public async stop() { @@ -169,26 +158,6 @@ export class LegacyService implements CoreService { } } - private async setupCliDevMode(config: LegacyConfig) { - const basePathProxy$ = this.coreContext.env.cliArgs.basePath - ? combineLatest([this.devConfig$, this.httpConfig$]).pipe( - first(), - map( - ([dev, http]) => - new BasePathProxyServer(this.coreContext.logger.get('server'), http, dev) - ) - ) - : EMPTY; - - // eslint-disable-next-line @typescript-eslint/no-var-requires - const { CliDevMode } = require('./cli_dev_mode'); - CliDevMode.fromCoreServices( - this.coreContext.env.cliArgs, - config, - await basePathProxy$.toPromise() - ); - } - private async createKbnServer( settings: LegacyVars, config: LegacyConfig, diff --git a/src/core/server/plugins/plugins_service.test.ts b/src/core/server/plugins/plugins_service.test.ts index 6a49dd963b4e8f..2d54648d229502 100644 --- a/src/core/server/plugins/plugins_service.test.ts +++ b/src/core/server/plugins/plugins_service.test.ts @@ -91,7 +91,7 @@ const createPlugin = ( }); }; -async function testSetup(options: { isDevCliParent?: boolean } = {}) { +async function testSetup() { mockPackage.raw = { branch: 'feature-v1', version: 'v1', @@ -103,10 +103,7 @@ async function testSetup(options: { isDevCliParent?: boolean } = {}) { }; coreId = Symbol('core'); - env = Env.createDefault(REPO_ROOT, { - ...getEnvOptions(), - isDevCliParent: options.isDevCliParent ?? false, - }); + env = Env.createDefault(REPO_ROOT, getEnvOptions()); config$ = new BehaviorSubject>({ plugins: { initialize: true } }); const rawConfigService = rawConfigServiceMock.create({ rawConfig$: config$ }); @@ -626,30 +623,3 @@ describe('PluginsService', () => { }); }); }); - -describe('PluginService when isDevCliParent is true', () => { - beforeEach(async () => { - await testSetup({ - isDevCliParent: true, - }); - }); - - describe('#discover()', () => { - it('does not try to run discovery', async () => { - await expect(pluginsService.discover({ environment: environmentSetup })).resolves - .toMatchInlineSnapshot(` - Object { - "pluginPaths": Array [], - "pluginTree": undefined, - "uiPlugins": Object { - "browserConfigs": Map {}, - "internal": Map {}, - "public": Map {}, - }, - } - `); - - expect(mockDiscover).not.toHaveBeenCalled(); - }); - }); -}); diff --git a/src/core/server/plugins/plugins_service.ts b/src/core/server/plugins/plugins_service.ts index 92b06d7b6a09b7..8b33e2cf4cc6be 100644 --- a/src/core/server/plugins/plugins_service.ts +++ b/src/core/server/plugins/plugins_service.ts @@ -7,7 +7,7 @@ */ import Path from 'path'; -import { Observable, EMPTY } from 'rxjs'; +import { Observable } from 'rxjs'; import { filter, first, map, mergeMap, tap, toArray } from 'rxjs/operators'; import { pick } from '@kbn/std'; @@ -75,11 +75,9 @@ export class PluginsService implements CoreService; private readonly pluginConfigDescriptors = new Map(); private readonly uiPluginInternalInfo = new Map(); - private readonly discoveryDisabled: boolean; constructor(private readonly coreContext: CoreContext) { this.log = coreContext.logger.get('plugins-service'); - this.discoveryDisabled = coreContext.env.isDevCliParent; this.pluginsSystem = new PluginsSystem(coreContext); this.configService = coreContext.configService; this.config$ = coreContext.configService @@ -90,14 +88,9 @@ export class PluginsService implements CoreService(); - const initialize = config.initialize && !this.coreContext.env.isDevCliParent; - if (initialize) { + if (config.initialize) { contracts = await this.pluginsSystem.setupPlugins(deps); this.registerPluginStaticDirs(deps); } else { @@ -131,7 +123,7 @@ export class PluginsService implements CoreService void ) { this.loggingSystem = new LoggingSystem(); @@ -87,10 +87,7 @@ export class Root { // Stream that maps config updates to logger updates, including update failures. const update$ = configService.getConfig$().pipe( // always read the logging config when the underlying config object is re-read - // except for the CLI process where we only apply the default logging config once - switchMap(() => - this.env.isDevCliParent ? of(undefined) : configService.atPath('logging') - ), + switchMap(() => configService.atPath('logging')), concatMap((config) => this.loggingSystem.upgrade(config)), // This specifically console.logs because we were not able to configure the logger. // eslint-disable-next-line no-console diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index cf1647ef5cec36..551471d3d3ba81 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -297,7 +297,7 @@ export class BasePath { // Warning: (ae-forgotten-export) The symbol "BootstrapArgs" needs to be exported by the entry point index.d.ts // // @internal (undocumented) -export function bootstrap({ configs, cliArgs, applyConfigOverrides, features, }: BootstrapArgs): Promise; +export function bootstrap({ configs, cliArgs, applyConfigOverrides }: BootstrapArgs): Promise; // @public export interface Capabilities { @@ -343,7 +343,7 @@ export const config: { pingTimeout: Type; logQueries: Type; ssl: import("@kbn/config-schema").ObjectType<{ - verificationMode: Type<"none" | "certificate" | "full">; + verificationMode: Type<"certificate" | "none" | "full">; certificateAuthorities: Type; certificate: Type; key: Type; @@ -1265,10 +1265,10 @@ export type KibanaResponseFactory = typeof kibanaResponseFactory; // @public export const kibanaResponseFactory: { - custom: | Buffer | Error | Stream | { + custom: | Error | Buffer | { message: string | Error; attributes?: Record | undefined; - } | undefined>(options: CustomHttpResponseOptions) => KibanaResponse; + } | Stream | undefined>(options: CustomHttpResponseOptions) => KibanaResponse; badRequest: (options?: ErrorHttpResponseOptions) => KibanaResponse; unauthorized: (options?: ErrorHttpResponseOptions) => KibanaResponse; forbidden: (options?: ErrorHttpResponseOptions) => KibanaResponse; diff --git a/src/core/server/server.test.ts b/src/core/server/server.test.ts index f2a2b10fdbfde5..fcf09b0295bcbd 100644 --- a/src/core/server/server.test.ts +++ b/src/core/server/server.test.ts @@ -205,19 +205,3 @@ test(`doesn't setup core services if legacy config validation fails`, async () = expect(mockLoggingService.setup).not.toHaveBeenCalled(); expect(mockI18nService.setup).not.toHaveBeenCalled(); }); - -test(`doesn't validate config if env.isDevCliParent is true`, async () => { - const devParentEnv = Env.createDefault(REPO_ROOT, { - ...getEnvOptions(), - isDevCliParent: true, - }); - - const server = new Server(rawConfigService, devParentEnv, logger); - await server.setup(); - - expect(mockEnsureValidConfiguration).not.toHaveBeenCalled(); - expect(mockContextService.setup).toHaveBeenCalled(); - expect(mockHttpService.setup).toHaveBeenCalled(); - expect(mockElasticsearchService.setup).toHaveBeenCalled(); - expect(mockSavedObjectsService.setup).toHaveBeenCalled(); -}); diff --git a/src/core/server/server.ts b/src/core/server/server.ts index ef5164a8c48e18..8905bcd28fe17c 100644 --- a/src/core/server/server.ts +++ b/src/core/server/server.ts @@ -120,13 +120,10 @@ export class Server { }); const legacyConfigSetup = await this.legacy.setupLegacyConfig(); - // rely on dev server to validate config, don't validate in the parent process - if (!this.env.isDevCliParent) { - // Immediately terminate in case of invalid configuration - // This needs to be done after plugin discovery - await this.configService.validate(); - await ensureValidConfiguration(this.configService, legacyConfigSetup); - } + // Immediately terminate in case of invalid configuration + // This needs to be done after plugin discovery + await this.configService.validate(); + await ensureValidConfiguration(this.configService, legacyConfigSetup); const contextServiceSetup = this.context.setup({ // We inject a fake "legacy plugin" with dependencies on every plugin so that legacy plugins: diff --git a/src/core/server/utils/index.ts b/src/core/server/utils/index.ts index e2dc2c7d99a93e..b0776c48f3bed2 100644 --- a/src/core/server/utils/index.ts +++ b/src/core/server/utils/index.ts @@ -6,6 +6,5 @@ * Side Public License, v 1. */ -export * from './crypto'; export * from './from_root'; export * from './package_json'; diff --git a/src/core/test_helpers/kbn_server.ts b/src/core/test_helpers/kbn_server.ts index 5e274712ad3a78..d702fed73778f1 100644 --- a/src/core/test_helpers/kbn_server.ts +++ b/src/core/test_helpers/kbn_server.ts @@ -70,7 +70,6 @@ export function createRootWithSettings( dist: false, ...cliArgs, }, - isDevCliParent: false, }); return new Root( diff --git a/yarn.lock b/yarn.lock index ce504fd9e96862..0bbfe98f5d1d86 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2618,6 +2618,10 @@ version "0.0.0" uid "" +"@kbn/cli-dev-mode@link:packages/kbn-cli-dev-mode": + version "0.0.0" + uid "" + "@kbn/config-schema@link:packages/kbn-config-schema": version "0.0.0" uid "" @@ -2626,6 +2630,10 @@ version "0.0.0" uid "" +"@kbn/crypto@link:packages/kbn-crypto": + version "0.0.0" + uid "" + "@kbn/dev-utils@link:packages/kbn-dev-utils": version "0.0.0" uid "" @@ -2690,6 +2698,10 @@ version "0.0.0" uid "" +"@kbn/server-http-tools@link:packages/kbn-server-http-tools": + version "0.0.0" + uid "" + "@kbn/std@link:packages/kbn-std": version "0.0.0" uid "" From 907b5c860d872cc3dad6f401cb67251f148b2aa6 Mon Sep 17 00:00:00 2001 From: Robert Oskamp Date: Tue, 30 Mar 2021 13:40:31 +0200 Subject: [PATCH 02/32] Update CODEOWNERS for ml-ui (#95773) --- .github/CODEOWNERS | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 3d44f46aca4ff7..f27885c1e32c34 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -119,18 +119,23 @@ # Machine Learning /x-pack/plugins/ml/ @elastic/ml-ui +/x-pack/test/accessibility/apps/ml.ts @elastic/ml-ui +/x-pack/test/accessibility/apps/ml_embeddables_in_dashboard.ts @elastic/ml-ui +/x-pack/test/api_integration/apis/ml/ @elastic/ml-ui +/x-pack/test/api_integration_basic/apis/ml/ @elastic/ml-ui /x-pack/test/functional/apps/ml/ @elastic/ml-ui +/x-pack/test/functional/es_archives/ml/ @elastic/ml-ui /x-pack/test/functional/services/ml/ @elastic/ml-ui -/x-pack/test/accessibility/apps/ml.ts @elastic/ml-ui +/x-pack/test/functional_basic/apps/ml/ @elastic/ml-ui +/x-pack/test/functional_with_es_ssl/apps/ml/ @elastic/ml-ui + # ML team owns and maintains the transform plugin despite it living in the Elasticsearch management section. /x-pack/plugins/transform/ @elastic/ml-ui -/x-pack/test/functional/apps/transform/ @elastic/ml-ui -/x-pack/test/functional/services/transform/ @elastic/ml-ui /x-pack/test/accessibility/apps/transform.ts @elastic/ml-ui -/x-pack/test/api_integration_basic/apis/ml/ @elastic/ml-ui -/x-pack/test/functional_basic/apps/ml/ @elastic/ml-ui - +/x-pack/test/api_integration/apis/transform/ @elastic/ml-ui /x-pack/test/api_integration_basic/apis/transform/ @elastic/ml-ui +/x-pack/test/functional/apps/transform/ @elastic/ml-ui +/x-pack/test/functional/services/transform/ @elastic/ml-ui /x-pack/test/functional_basic/apps/transform/ @elastic/ml-ui # Maps From b58dd3efe8ba04c43665f3cd823ca19e94de2289 Mon Sep 17 00:00:00 2001 From: Liza Katz Date: Tue, 30 Mar 2021 15:24:30 +0300 Subject: [PATCH 03/32] [Sessions] Extract search abort controllers logic into a separate class (#95688) * simplify abort controller logic and extract it into a class * docs * delete unused tests * code review * code review * code review --- ...lic.searchinterceptor.handlesearcherror.md | 4 +- ...n-plugins-data-public.searchinterceptor.md | 2 +- src/plugins/data/public/public.api.md | 14 +-- .../data/public/search/search_interceptor.ts | 86 ++------------ .../kibana_utils/common/abort_utils.test.ts | 89 +-------------- .../kibana_utils/common/abort_utils.ts | 29 ----- src/plugins/kibana_utils/common/index.ts | 2 +- src/plugins/kibana_utils/public/index.ts | 1 - src/plugins/kibana_utils/server/index.ts | 1 - .../search/search_abort_controller.test.ts | 105 ++++++++++++++++++ .../public/search/search_abort_controller.ts | 78 +++++++++++++ .../public/search/search_interceptor.ts | 33 +++--- 12 files changed, 216 insertions(+), 228 deletions(-) create mode 100644 x-pack/plugins/data_enhanced/public/search/search_abort_controller.test.ts create mode 100644 x-pack/plugins/data_enhanced/public/search/search_abort_controller.ts diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.handlesearcherror.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.handlesearcherror.md index 5f8966f0227ac5..f6421d65bc551d 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.handlesearcherror.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.handlesearcherror.md @@ -7,7 +7,7 @@ Signature: ```typescript -protected handleSearchError(e: KibanaServerError | AbortError, timeoutSignal: AbortSignal, options?: ISearchOptions): Error; +protected handleSearchError(e: KibanaServerError | AbortError, options?: ISearchOptions, isTimeout?: boolean): Error; ``` ## Parameters @@ -15,8 +15,8 @@ protected handleSearchError(e: KibanaServerError | AbortError, timeoutSignal: Ab | Parameter | Type | Description | | --- | --- | --- | | e | KibanaServerError | AbortError | | -| timeoutSignal | AbortSignal | | | options | ISearchOptions | | +| isTimeout | boolean | | Returns: diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.md index 2247813562dc72..9d18309fc07be1 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.md @@ -27,7 +27,7 @@ export declare class SearchInterceptor | Method | Modifiers | Description | | --- | --- | --- | | [getTimeoutMode()](./kibana-plugin-plugins-data-public.searchinterceptor.gettimeoutmode.md) | | | -| [handleSearchError(e, timeoutSignal, options)](./kibana-plugin-plugins-data-public.searchinterceptor.handlesearcherror.md) | | | +| [handleSearchError(e, options, isTimeout)](./kibana-plugin-plugins-data-public.searchinterceptor.handlesearcherror.md) | | | | [search(request, options)](./kibana-plugin-plugins-data-public.searchinterceptor.search.md) | | Searches using the given search method. Overrides the AbortSignal with one that will abort either when the request times out, or when the original AbortSignal is aborted. Updates pendingCount$ when the request is started/finalized. | | [showError(e)](./kibana-plugin-plugins-data-public.searchinterceptor.showerror.md) | | | diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index e4085abe140501..746d035e9bfb6a 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -2322,8 +2322,6 @@ export interface SearchError { // @public (undocumented) export class SearchInterceptor { constructor(deps: SearchInterceptorDeps); - // @internal - protected abortController: AbortController; // @internal (undocumented) protected application: CoreStart['application']; // (undocumented) @@ -2334,22 +2332,12 @@ export class SearchInterceptor { // Warning: (ae-forgotten-export) The symbol "AbortError" needs to be exported by the entry point index.d.ts // // (undocumented) - protected handleSearchError(e: KibanaServerError | AbortError, timeoutSignal: AbortSignal, options?: ISearchOptions): Error; + protected handleSearchError(e: KibanaServerError | AbortError, options?: ISearchOptions, isTimeout?: boolean): Error; // @internal protected pendingCount$: BehaviorSubject; // @internal (undocumented) protected runSearch(request: IKibanaSearchRequest, options?: ISearchOptions): Promise; search(request: IKibanaSearchRequest, options?: ISearchOptions): Observable; - // @internal (undocumented) - protected setupAbortSignal({ abortSignal, timeout, }: { - abortSignal?: AbortSignal; - timeout?: number; - }): { - timeoutSignal: AbortSignal; - combinedSignal: AbortSignal; - cleanup: () => void; - abort: () => void; - }; // (undocumented) showError(e: Error): void; } diff --git a/src/plugins/data/public/search/search_interceptor.ts b/src/plugins/data/public/search/search_interceptor.ts index f5a2dc0571fdcf..3df2313f837982 100644 --- a/src/plugins/data/public/search/search_interceptor.ts +++ b/src/plugins/data/public/search/search_interceptor.ts @@ -7,7 +7,7 @@ */ import { memoize } from 'lodash'; -import { BehaviorSubject, throwError, timer, defer, from, Observable, NEVER } from 'rxjs'; +import { BehaviorSubject, throwError, defer, from, Observable } from 'rxjs'; import { catchError, finalize } from 'rxjs/operators'; import { PublicMethodsOf } from '@kbn/utility-types'; import { CoreStart, CoreSetup, ToastsSetup } from 'kibana/public'; @@ -30,11 +30,7 @@ import { getHttpError, } from './errors'; import { toMountPoint } from '../../../kibana_react/public'; -import { - AbortError, - getCombinedAbortSignal, - KibanaServerError, -} from '../../../kibana_utils/public'; +import { AbortError, KibanaServerError } from '../../../kibana_utils/public'; import { ISessionService } from './session'; export interface SearchInterceptorDeps { @@ -48,12 +44,6 @@ export interface SearchInterceptorDeps { } export class SearchInterceptor { - /** - * `abortController` used to signal all searches to abort. - * @internal - */ - protected abortController = new AbortController(); - /** * Observable that emits when the number of pending requests changes. * @internal @@ -98,10 +88,10 @@ export class SearchInterceptor { */ protected handleSearchError( e: KibanaServerError | AbortError, - timeoutSignal: AbortSignal, - options?: ISearchOptions + options?: ISearchOptions, + isTimeout?: boolean ): Error { - if (timeoutSignal.aborted || e.message === 'Request timed out') { + if (isTimeout || e.message === 'Request timed out') { // Handle a client or a server side timeout const err = new SearchTimeoutError(e, this.getTimeoutMode()); @@ -154,60 +144,6 @@ export class SearchInterceptor { ); } - /** - * @internal - */ - protected setupAbortSignal({ - abortSignal, - timeout, - }: { - abortSignal?: AbortSignal; - timeout?: number; - }) { - // Schedule this request to automatically timeout after some interval - const timeoutController = new AbortController(); - const { signal: timeoutSignal } = timeoutController; - const timeout$ = timeout ? timer(timeout) : NEVER; - const subscription = timeout$.subscribe(() => { - this.deps.usageCollector?.trackQueryTimedOut(); - timeoutController.abort(); - }); - - const selfAbortController = new AbortController(); - - // Get a combined `AbortSignal` that will be aborted whenever the first of the following occurs: - // 1. The internal abort controller aborts - // 2. The request times out - // 3. abort() is called on `selfAbortController`. This is used by session service to abort all pending searches that it tracks - // in the current session - // 4. The passed-in signal aborts (e.g. when re-fetching, or whenever the app determines) - const signals = [ - this.abortController.signal, - timeoutSignal, - selfAbortController.signal, - ...(abortSignal ? [abortSignal] : []), - ]; - - const { signal: combinedSignal, cleanup: cleanupCombinedSignal } = getCombinedAbortSignal( - signals - ); - const cleanup = () => { - subscription.unsubscribe(); - combinedSignal.removeEventListener('abort', cleanup); - cleanupCombinedSignal(); - }; - combinedSignal.addEventListener('abort', cleanup); - - return { - timeoutSignal, - combinedSignal, - cleanup, - abort: () => { - selfAbortController.abort(); - }, - }; - } - private showTimeoutErrorToast = (e: SearchTimeoutError, sessionId?: string) => { this.deps.toasts.addDanger({ title: 'Timed out', @@ -245,25 +181,21 @@ export class SearchInterceptor { */ public search( request: IKibanaSearchRequest, - options?: ISearchOptions + options: ISearchOptions = {} ): Observable { // Defer the following logic until `subscribe` is actually called return defer(() => { - if (options?.abortSignal?.aborted) { + if (options.abortSignal?.aborted) { return throwError(new AbortError()); } - const { timeoutSignal, combinedSignal, cleanup } = this.setupAbortSignal({ - abortSignal: options?.abortSignal, - }); this.pendingCount$.next(this.pendingCount$.getValue() + 1); - return from(this.runSearch(request, { ...options, abortSignal: combinedSignal })).pipe( + return from(this.runSearch(request, options)).pipe( catchError((e: Error | AbortError) => { - return throwError(this.handleSearchError(e, timeoutSignal, options)); + return throwError(this.handleSearchError(e, options)); }), finalize(() => { this.pendingCount$.next(this.pendingCount$.getValue() - 1); - cleanup(); }) ); }); diff --git a/src/plugins/kibana_utils/common/abort_utils.test.ts b/src/plugins/kibana_utils/common/abort_utils.test.ts index 1f8a1ef3d84c58..0d34a7852fb440 100644 --- a/src/plugins/kibana_utils/common/abort_utils.test.ts +++ b/src/plugins/kibana_utils/common/abort_utils.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { AbortError, abortSignalToPromise, getCombinedAbortSignal } from './abort_utils'; +import { AbortError, abortSignalToPromise } from './abort_utils'; jest.useFakeTimers(); @@ -66,91 +66,4 @@ describe('AbortUtils', () => { }); }); }); - - describe('getCombinedAbortSignal', () => { - test('should return an AbortSignal', () => { - const signal = getCombinedAbortSignal([]).signal; - expect(signal).toBeInstanceOf(AbortSignal); - }); - - test('should not abort if none of the signals abort', async () => { - const controller1 = new AbortController(); - const controller2 = new AbortController(); - setTimeout(() => controller1.abort(), 2000); - setTimeout(() => controller2.abort(), 1000); - const signal = getCombinedAbortSignal([controller1.signal, controller2.signal]).signal; - expect(signal.aborted).toBe(false); - jest.advanceTimersByTime(500); - await flushPromises(); - expect(signal.aborted).toBe(false); - }); - - test('should abort when the first signal aborts', async () => { - const controller1 = new AbortController(); - const controller2 = new AbortController(); - setTimeout(() => controller1.abort(), 2000); - setTimeout(() => controller2.abort(), 1000); - const signal = getCombinedAbortSignal([controller1.signal, controller2.signal]).signal; - expect(signal.aborted).toBe(false); - jest.advanceTimersByTime(1000); - await flushPromises(); - expect(signal.aborted).toBe(true); - }); - - test('should be aborted if any of the signals is already aborted', async () => { - const controller1 = new AbortController(); - const controller2 = new AbortController(); - controller1.abort(); - const signal = getCombinedAbortSignal([controller1.signal, controller2.signal]).signal; - expect(signal.aborted).toBe(true); - }); - - describe('cleanup listener', () => { - const createMockController = () => { - const controller = new AbortController(); - const spyAddListener = jest.spyOn(controller.signal, 'addEventListener'); - const spyRemoveListener = jest.spyOn(controller.signal, 'removeEventListener'); - return { - controller, - getTotalListeners: () => - Math.max(spyAddListener.mock.calls.length - spyRemoveListener.mock.calls.length, 0), - }; - }; - - test('cleanup should cleanup inner listeners', () => { - const controller1 = createMockController(); - const controller2 = createMockController(); - - const { cleanup } = getCombinedAbortSignal([ - controller1.controller.signal, - controller2.controller.signal, - ]); - - expect(controller1.getTotalListeners()).toBe(1); - expect(controller2.getTotalListeners()).toBe(1); - - cleanup(); - - expect(controller1.getTotalListeners()).toBe(0); - expect(controller2.getTotalListeners()).toBe(0); - }); - - test('abort should cleanup inner listeners', async () => { - const controller1 = createMockController(); - const controller2 = createMockController(); - - getCombinedAbortSignal([controller1.controller.signal, controller2.controller.signal]); - - expect(controller1.getTotalListeners()).toBe(1); - expect(controller2.getTotalListeners()).toBe(1); - - controller1.controller.abort(); - - await flushPromises(); - - expect(controller1.getTotalListeners()).toBe(0); - expect(controller2.getTotalListeners()).toBe(0); - }); - }); - }); }); diff --git a/src/plugins/kibana_utils/common/abort_utils.ts b/src/plugins/kibana_utils/common/abort_utils.ts index f4c750745a605d..051f947b68c1b6 100644 --- a/src/plugins/kibana_utils/common/abort_utils.ts +++ b/src/plugins/kibana_utils/common/abort_utils.ts @@ -45,32 +45,3 @@ export function abortSignalToPromise( return { promise, cleanup }; } - -/** - * Returns an `AbortSignal` that will be aborted when the first of the given signals aborts. - * - * @param signals - */ -export function getCombinedAbortSignal( - signals: AbortSignal[] -): { signal: AbortSignal; cleanup: () => void } { - const controller = new AbortController(); - let cleanup = () => {}; - - if (signals.some((signal) => signal.aborted)) { - controller.abort(); - } else { - const promises = signals.map((signal) => abortSignalToPromise(signal)); - cleanup = () => { - promises.forEach((p) => p.cleanup()); - controller.signal.removeEventListener('abort', cleanup); - }; - controller.signal.addEventListener('abort', cleanup); - Promise.race(promises.map((p) => p.promise)).catch(() => { - cleanup(); - controller.abort(); - }); - } - - return { signal: controller.signal, cleanup }; -} diff --git a/src/plugins/kibana_utils/common/index.ts b/src/plugins/kibana_utils/common/index.ts index 398bf1415c0050..76a7cb2855c6e0 100644 --- a/src/plugins/kibana_utils/common/index.ts +++ b/src/plugins/kibana_utils/common/index.ts @@ -13,7 +13,7 @@ export * from './ui'; export * from './state_containers'; export * from './typed_json'; export * from './errors'; -export { AbortError, abortSignalToPromise, getCombinedAbortSignal } from './abort_utils'; +export { AbortError, abortSignalToPromise } from './abort_utils'; export { createGetterSetter, Get, Set } from './create_getter_setter'; export { distinctUntilChangedWithInitialValue } from './distinct_until_changed_with_initial_value'; export { url } from './url'; diff --git a/src/plugins/kibana_utils/public/index.ts b/src/plugins/kibana_utils/public/index.ts index 9a94757cdcb7ac..75c52e1301ea57 100644 --- a/src/plugins/kibana_utils/public/index.ts +++ b/src/plugins/kibana_utils/public/index.ts @@ -15,7 +15,6 @@ export { fieldWildcardFilter, fieldWildcardMatcher, Get, - getCombinedAbortSignal, JsonArray, JsonObject, JsonValue, diff --git a/src/plugins/kibana_utils/server/index.ts b/src/plugins/kibana_utils/server/index.ts index babc5c4a201ee2..483c5aa92b45ed 100644 --- a/src/plugins/kibana_utils/server/index.ts +++ b/src/plugins/kibana_utils/server/index.ts @@ -13,7 +13,6 @@ export { fieldWildcardFilter, fieldWildcardMatcher, Get, - getCombinedAbortSignal, Set, url, } from '../common'; diff --git a/x-pack/plugins/data_enhanced/public/search/search_abort_controller.test.ts b/x-pack/plugins/data_enhanced/public/search/search_abort_controller.test.ts new file mode 100644 index 00000000000000..68282c1e947f70 --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/search_abort_controller.test.ts @@ -0,0 +1,105 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { SearchAbortController } from './search_abort_controller'; + +const timeTravel = (msToRun = 0) => { + jest.advanceTimersByTime(msToRun); + return new Promise((resolve) => setImmediate(resolve)); +}; + +describe('search abort controller', () => { + test('is not aborted when empty', () => { + const sac = new SearchAbortController(); + expect(sac.getSignal().aborted).toBe(false); + }); + + test('immediately aborts when passed an aborted signal in the constructor', () => { + const controller = new AbortController(); + controller.abort(); + const sac = new SearchAbortController(controller.signal); + expect(sac.getSignal().aborted).toBe(true); + }); + + test('aborts when input signal is aborted', () => { + const controller = new AbortController(); + const sac = new SearchAbortController(controller.signal); + expect(sac.getSignal().aborted).toBe(false); + controller.abort(); + expect(sac.getSignal().aborted).toBe(true); + }); + + test('aborts when all input signals are aborted', () => { + const controller = new AbortController(); + const sac = new SearchAbortController(controller.signal); + + const controller2 = new AbortController(); + sac.addAbortSignal(controller2.signal); + expect(sac.getSignal().aborted).toBe(false); + controller.abort(); + expect(sac.getSignal().aborted).toBe(false); + controller2.abort(); + expect(sac.getSignal().aborted).toBe(true); + }); + + test('aborts explicitly even if all inputs are not aborted', () => { + const controller = new AbortController(); + const sac = new SearchAbortController(controller.signal); + + const controller2 = new AbortController(); + sac.addAbortSignal(controller2.signal); + + expect(sac.getSignal().aborted).toBe(false); + sac.abort(); + expect(sac.getSignal().aborted).toBe(true); + }); + + test('doesnt abort, if cleared', () => { + const controller = new AbortController(); + const sac = new SearchAbortController(controller.signal); + expect(sac.getSignal().aborted).toBe(false); + sac.cleanup(); + controller.abort(); + expect(sac.getSignal().aborted).toBe(false); + }); + + describe('timeout abort', () => { + beforeEach(() => { + jest.useFakeTimers(); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + test('doesnt abort on timeout, if cleared', () => { + const sac = new SearchAbortController(undefined, 100); + expect(sac.getSignal().aborted).toBe(false); + sac.cleanup(); + timeTravel(100); + expect(sac.getSignal().aborted).toBe(false); + }); + + test('aborts on timeout, even if no signals passed in', () => { + const sac = new SearchAbortController(undefined, 100); + expect(sac.getSignal().aborted).toBe(false); + timeTravel(100); + expect(sac.getSignal().aborted).toBe(true); + expect(sac.isTimeout()).toBe(true); + }); + + test('aborts on timeout, even if there are unaborted signals', () => { + const controller = new AbortController(); + const sac = new SearchAbortController(controller.signal, 100); + + expect(sac.getSignal().aborted).toBe(false); + timeTravel(100); + expect(sac.getSignal().aborted).toBe(true); + expect(sac.isTimeout()).toBe(true); + }); + }); +}); diff --git a/x-pack/plugins/data_enhanced/public/search/search_abort_controller.ts b/x-pack/plugins/data_enhanced/public/search/search_abort_controller.ts new file mode 100644 index 00000000000000..4482a7771dc285 --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/search_abort_controller.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Subscription, timer } from 'rxjs'; + +export enum AbortReason { + Timeout = 'timeout', +} + +export class SearchAbortController { + private inputAbortSignals: AbortSignal[] = new Array(); + private abortController: AbortController = new AbortController(); + private timeoutSub?: Subscription; + private destroyed = false; + private reason?: AbortReason; + + constructor(abortSignal?: AbortSignal, timeout?: number) { + if (abortSignal) { + this.addAbortSignal(abortSignal); + } + + if (timeout) { + this.timeoutSub = timer(timeout).subscribe(() => { + this.reason = AbortReason.Timeout; + this.abortController.abort(); + this.timeoutSub!.unsubscribe(); + }); + } + } + + private abortHandler = () => { + const allAborted = this.inputAbortSignals.every((signal) => signal.aborted); + if (allAborted) { + this.abortController.abort(); + this.cleanup(); + } + }; + + public cleanup() { + this.destroyed = true; + this.timeoutSub?.unsubscribe(); + this.inputAbortSignals.forEach((abortSignal) => { + abortSignal.removeEventListener('abort', this.abortHandler); + }); + } + + public addAbortSignal(inputSignal: AbortSignal) { + if (this.destroyed) { + return; + } + + this.inputAbortSignals.push(inputSignal); + + if (inputSignal.aborted) { + this.abortHandler(); + } else { + // abort our internal controller if the input signal aborts + inputSignal.addEventListener('abort', this.abortHandler); + } + } + + public getSignal() { + return this.abortController.signal; + } + + public abort() { + this.cleanup(); + this.abortController.abort(); + } + + public isTimeout() { + return this.reason === AbortReason.Timeout; + } +} diff --git a/x-pack/plugins/data_enhanced/public/search/search_interceptor.ts b/x-pack/plugins/data_enhanced/public/search/search_interceptor.ts index 0dfec1a35d9006..b9d8553d3dc5a8 100644 --- a/x-pack/plugins/data_enhanced/public/search/search_interceptor.ts +++ b/x-pack/plugins/data_enhanced/public/search/search_interceptor.ts @@ -17,6 +17,7 @@ import { SearchSessionState, } from '../../../../../src/plugins/data/public'; import { ENHANCED_ES_SEARCH_STRATEGY, IAsyncSearchOptions, pollSearch } from '../../common'; +import { SearchAbortController } from './search_abort_controller'; export class EnhancedSearchInterceptor extends SearchInterceptor { private uiSettingsSub: Subscription; @@ -47,31 +48,30 @@ export class EnhancedSearchInterceptor extends SearchInterceptor { } public search({ id, ...request }: IKibanaSearchRequest, options: IAsyncSearchOptions = {}) { - const { combinedSignal, timeoutSignal, cleanup, abort } = this.setupAbortSignal({ - abortSignal: options.abortSignal, - timeout: this.searchTimeout, - }); - const strategy = options?.strategy ?? ENHANCED_ES_SEARCH_STRATEGY; - const searchOptions = { ...options, strategy, abortSignal: combinedSignal }; + const searchOptions = { + strategy: ENHANCED_ES_SEARCH_STRATEGY, + ...options, + }; + const { sessionId, strategy, abortSignal } = searchOptions; const search = () => this.runSearch({ id, ...request }, searchOptions); + const searchAbortController = new SearchAbortController(abortSignal, this.searchTimeout); this.pendingCount$.next(this.pendingCount$.getValue() + 1); - - const untrackSearch = - this.deps.session.isCurrentSession(options.sessionId) && - this.deps.session.trackSearch({ abort }); + const untrackSearch = this.deps.session.isCurrentSession(options.sessionId) + ? this.deps.session.trackSearch({ abort: () => searchAbortController.abort() }) + : undefined; // track if this search's session will be send to background // if yes, then we don't need to cancel this search when it is aborted let isSavedToBackground = false; const savedToBackgroundSub = - this.deps.session.isCurrentSession(options.sessionId) && + this.deps.session.isCurrentSession(sessionId) && this.deps.session.state$ .pipe( skip(1), // ignore any state, we are only interested in transition x -> BackgroundLoading filter( (state) => - this.deps.session.isCurrentSession(options.sessionId) && + this.deps.session.isCurrentSession(sessionId) && state === SearchSessionState.BackgroundLoading ), take(1) @@ -84,15 +84,18 @@ export class EnhancedSearchInterceptor extends SearchInterceptor { if (id && !isSavedToBackground) this.deps.http.delete(`/internal/search/${strategy}/${id}`); }); - return pollSearch(search, cancel, { ...options, abortSignal: combinedSignal }).pipe( + return pollSearch(search, cancel, { + ...options, + abortSignal: searchAbortController.getSignal(), + }).pipe( tap((response) => (id = response.id)), catchError((e: Error) => { cancel(); - return throwError(this.handleSearchError(e, timeoutSignal, options)); + return throwError(this.handleSearchError(e, options, searchAbortController.isTimeout())); }), finalize(() => { this.pendingCount$.next(this.pendingCount$.getValue() - 1); - cleanup(); + searchAbortController.cleanup(); if (untrackSearch && this.deps.session.isCurrentSession(options.sessionId)) { // untrack if this search still belongs to current session untrackSearch(); From 3abb79a17939ae70d465a97472e341bd8ad2ec0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20C=C3=B4t=C3=A9?= Date: Tue, 30 Mar 2021 08:27:28 -0400 Subject: [PATCH 04/32] Create new alerting HTTP APIs that use the new terminology (#93977) * Move current alert HTTP APIs to legacy folder (#93943) * Move current HTTP APIs to legacy folder * Rename BASE_ALERT_API_PATH to LEGACY_BASE_ALERT_API_PATH * Fix failing tests and extra files * Create new rule HTTP APIs (#93980) * Move current HTTP APIs to legacy folder * Rename BASE_ALERT_API_PATH to LEGACY_BASE_ALERT_API_PATH * Fix failing tests and extra files * Move current alert HTTP APIs to legacy folder (#93943) * Move current HTTP APIs to legacy folder * Rename BASE_ALERT_API_PATH to LEGACY_BASE_ALERT_API_PATH * Fix failing tests and extra files * Add necessary files * Create rule route * Get rule API * Update rule API * Delete rule route * Aggregate rules API * Disable rule API * Enable rule API * Find rules API * Fix Update API * Get rule alert summary API * Get rule state API * Health API * Rule types API * Mute all API * Mute alert API * Unmute all API * Unmute alert route * Update API key API * corrected tpye by making it much more complicated * removed unneeded cocde * Fixes * Add back health route * mutedInstanceIds -> mutedAlertIds * lastRun -> last_run * alert_type_state -> rule_type_state & alert_instances -> alerts Co-authored-by: Gidi Meir Morris * Create docs for new rule HTTP APIs, deprecate old docs (#94745) * Create docs for new APIs, deprecate old docs * Remove connector_type_id * Update docs * Add link to legacy APIs from rules API docs * Remove connector_type_id references * [DOCS] Add legacy APIs to index.asciidoc * Fix camel case Co-authored-by: lcawl * Make alerting tests use new rules APIs (#95159) * Make API integration tests use new HTTP APIs * Fix end to end tests * Fix test failures * Fix more test failures * Rename some files * Add tests for legacy APIs (#95333) * Initial commit (#95457) * Move some new alerting APIs to /internal (#95461) * Initial commit * Update README.md * Use internal API * Merge deprecated warning w/ alternative solution * Update API docs Co-authored-by: Gidi Meir Morris Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: lcawl --- api_docs/alerting.json | 70 +- api_docs/apm.json | 2 +- api_docs/console.json | 2 +- api_docs/core.json | 118 +- api_docs/core_http.json | 2 +- api_docs/core_saved_objects.json | 934 ++++++--- api_docs/data.json | 67 +- api_docs/data_autocomplete.json | 2 +- api_docs/data_enhanced.json | 6 +- api_docs/data_index_patterns.json | 30 +- api_docs/data_search.json | 627 ++---- api_docs/data_search.mdx | 3 + api_docs/event_log.json | 10 +- api_docs/expressions.json | 280 +-- api_docs/fleet.json | 313 ++- api_docs/global_search.json | 2 +- api_docs/index_management.json | 8 +- api_docs/infra.json | 6 +- api_docs/lens.json | 100 +- api_docs/lists.json | 182 ++ api_docs/ml.json | 1674 +++-------------- api_docs/observability.json | 272 ++- api_docs/observability.mdx | 3 - api_docs/reporting.json | 4 +- api_docs/security.json | 2 +- api_docs/security_solution.json | 36 +- api_docs/telemetry.json | 16 +- api_docs/telemetry_collection_manager.json | 2 +- api_docs/telemetry_collection_xpack.json | 160 +- api_docs/telemetry_collection_xpack.mdx | 4 +- api_docs/vis_type_timeseries.json | 2 +- docs/api/alerting.asciidoc | 47 + docs/api/alerting/create_rule.asciidoc | 196 ++ docs/api/alerting/delete_rule.asciidoc | 41 + docs/api/alerting/disable_rule.asciidoc | 39 + docs/api/alerting/enable_rule.asciidoc | 39 + docs/api/alerting/find_rules.asciidoc | 127 ++ docs/api/alerting/get_rules.asciidoc | 75 + docs/api/alerting/health.asciidoc | 93 + .../legacy}/create.asciidoc | 6 +- .../legacy}/delete.asciidoc | 6 +- .../legacy}/disable.asciidoc | 6 +- .../legacy}/enable.asciidoc | 6 +- .../{alerts => alerting/legacy}/find.asciidoc | 6 +- .../{alerts => alerting/legacy}/get.asciidoc | 6 +- .../legacy}/health.asciidoc | 6 +- docs/api/alerting/legacy/index.asciidoc | 18 + .../{alerts => alerting/legacy}/list.asciidoc | 6 +- .../{alerts => alerting/legacy}/mute.asciidoc | 6 +- .../legacy}/mute_all.asciidoc | 6 +- .../legacy}/unmute.asciidoc | 6 +- .../legacy}/unmute_all.asciidoc | 6 +- .../legacy}/update.asciidoc | 6 +- docs/api/alerting/list_rule_types.asciidoc | 135 ++ docs/api/alerting/mute_alert.asciidoc | 42 + docs/api/alerting/mute_all_alerts.asciidoc | 39 + docs/api/alerting/unmute_alert.asciidoc | 42 + docs/api/alerting/unmute_all_alerts.asciidoc | 39 + docs/api/alerting/update_rule.asciidoc | 136 ++ docs/api/alerts.asciidoc | 42 - docs/user/api.asciidoc | 2 +- .../public/components/view_alert.tsx | 10 +- .../public/components/view_astros_alert.tsx | 10 +- x-pack/plugins/alerting/README.md | 18 +- x-pack/plugins/alerting/common/index.ts | 4 +- x-pack/plugins/alerting/public/alert_api.ts | 8 +- .../server/alerts_client/alerts_client.ts | 8 +- .../alerting/server/lib/errors/index.ts | 17 + x-pack/plugins/alerting/server/lib/index.ts | 9 +- x-pack/plugins/alerting/server/plugin.ts | 38 +- .../server/routes/aggregate_rules.test.ts | 150 ++ .../alerting/server/routes/aggregate_rules.ts | 79 + .../server/routes/create_rule.test.ts | 298 +++ .../alerting/server/routes/create_rule.ts | 141 ++ .../server/routes/delete_rule.test.ts | 109 ++ .../alerting/server/routes/delete_rule.ts | 38 + .../server/routes/disable_rule.test.ts | 81 + .../alerting/server/routes/disable_rule.ts | 45 + .../server/routes/enable_rule.test.ts | 81 + .../alerting/server/routes/enable_rule.ts | 45 + .../alerting/server/routes/find_rules.test.ts | 148 ++ .../alerting/server/routes/find_rules.ts | 143 ++ .../alerting/server/routes/get_rule.test.ts | 169 ++ .../alerting/server/routes/get_rule.ts | 84 + .../routes/get_rule_alert_summary.test.ts | 106 ++ .../server/routes/get_rule_alert_summary.ts | 77 + .../server/routes/get_rule_state.test.ts | 150 ++ .../alerting/server/routes/get_rule_state.ts | 54 + .../alerting/server/routes/health.test.ts | 74 +- .../plugins/alerting/server/routes/health.ts | 98 +- .../plugins/alerting/server/routes/index.ts | 64 +- .../routes/{ => legacy}/aggregate.test.ts | 10 +- .../server/routes/{ => legacy}/aggregate.ts | 14 +- .../server/routes/{ => legacy}/create.test.ts | 14 +- .../server/routes/{ => legacy}/create.ts | 30 +- .../server/routes/{ => legacy}/delete.test.ts | 10 +- .../server/routes/{ => legacy}/delete.ts | 10 +- .../routes/{ => legacy}/disable.test.ts | 10 +- .../server/routes/{ => legacy}/disable.ts | 12 +- .../server/routes/{ => legacy}/enable.test.ts | 10 +- .../server/routes/{ => legacy}/enable.ts | 14 +- .../server/routes/{ => legacy}/find.test.ts | 10 +- .../server/routes/{ => legacy}/find.ts | 14 +- .../server/routes/{ => legacy}/get.test.ts | 12 +- .../server/routes/{ => legacy}/get.ts | 10 +- .../get_alert_instance_summary.test.ts | 10 +- .../get_alert_instance_summary.ts | 10 +- .../{ => legacy}/get_alert_state.test.ts | 8 +- .../routes/{ => legacy}/get_alert_state.ts | 10 +- .../server/routes/legacy/health.test.ts | 336 ++++ .../alerting/server/routes/legacy/health.ts | 71 + .../alerting/server/routes/legacy/index.ts | 53 + .../{ => legacy}/list_alert_types.test.ts | 14 +- .../routes/{ => legacy}/list_alert_types.ts | 10 +- .../routes/{ => legacy}/mute_all.test.ts | 10 +- .../server/routes/{ => legacy}/mute_all.ts | 12 +- .../routes/{ => legacy}/mute_instance.test.ts | 10 +- .../routes/{ => legacy}/mute_instance.ts | 16 +- .../routes/{ => legacy}/unmute_all.test.ts | 10 +- .../server/routes/{ => legacy}/unmute_all.ts | 12 +- .../{ => legacy}/unmute_instance.test.ts | 10 +- .../routes/{ => legacy}/unmute_instance.ts | 12 +- .../server/routes/{ => legacy}/update.test.ts | 14 +- .../server/routes/{ => legacy}/update.ts | 20 +- .../{ => legacy}/update_api_key.test.ts | 10 +- .../routes/{ => legacy}/update_api_key.ts | 14 +- .../alerting/server/routes/lib/index.ts | 15 + .../server/routes/lib/rewrite_request_case.ts | 82 + .../routes/lib/verify_access_and_context.ts | 34 + .../alerting/server/routes/mute_alert.test.ts | 86 + .../alerting/server/routes/mute_alert.ts | 55 + .../server/routes/mute_all_rule.test.ts | 80 + .../alerting/server/routes/mute_all_rule.ts | 45 + .../alerting/server/routes/rule_types.test.ts | 223 +++ .../alerting/server/routes/rule_types.ts | 56 + .../server/routes/unmute_alert.test.ts | 86 + .../alerting/server/routes/unmute_alert.ts | 55 + .../server/routes/unmute_all_rule.test.ts | 80 + .../alerting/server/routes/unmute_all_rule.ts | 45 + .../server/routes/update_rule.test.ts | 215 +++ .../alerting/server/routes/update_rule.ts | 147 ++ .../server/routes/update_rule_api_key.test.ts | 82 + .../server/routes/update_rule_api_key.ts | 45 + .../public/alerts/configuration.tsx | 10 +- .../monitoring/server/alerts/base_alert.ts | 2 +- .../server/routes/api/v1/alerts/enable.ts | 4 +- .../notifications/create_notifications.ts | 4 +- .../detection_engine/rules/create_rules.ts | 4 +- .../rules/install_prepacked_rules.ts | 6 +- .../public/application/constants/index.ts | 2 +- .../public/application/lib/alert_api.ts | 34 +- .../tests/alerts/basic_noop_alert_type.ts | 2 +- .../tests/alerts/gold_noop_alert_type.ts | 4 +- .../common/lib/alert_utils.ts | 50 +- .../common/lib/get_test_alert_data.ts | 4 +- .../tests/actions/get_all.ts | 2 +- .../tests/alerting/alerts.ts | 40 +- .../tests/alerting/create.ts | 91 +- .../tests/alerting/delete.ts | 69 +- .../tests/alerting/disable.ts | 48 +- .../tests/alerting/enable.ts | 54 +- .../tests/alerting/event_log.ts | 6 +- .../tests/alerting/execution_status.ts | 14 +- .../tests/alerting/find.ts | 140 +- .../security_and_spaces/tests/alerting/get.ts | 64 +- .../alerting/get_alert_instance_summary.ts | 50 +- .../tests/alerting/get_alert_state.ts | 26 +- .../tests/alerting/index.ts | 2 +- .../tests/alerting/mustache_templates.ts | 12 +- .../tests/alerting/mute_all.ts | 42 +- .../tests/alerting/mute_instance.ts | 54 +- .../tests/alerting/rbac_legacy.ts | 2 +- .../{list_alert_types.ts => rule_types.ts} | 68 +- .../tests/alerting/unmute_all.ts | 50 +- .../tests/alerting/unmute_instance.ts | 58 +- .../tests/alerting/update.ts | 273 +-- .../tests/alerting/update_api_key.ts | 54 +- .../spaces_only/tests/alerting/aggregate.ts | 95 +- .../spaces_only/tests/alerting/alerts_base.ts | 58 +- .../builtin_alert_types/es_query/alert.ts | 13 +- .../index_threshold/alert.ts | 13 +- .../spaces_only/tests/alerting/create.ts | 152 +- .../spaces_only/tests/alerting/delete.ts | 30 +- .../spaces_only/tests/alerting/disable.ts | 39 +- .../spaces_only/tests/alerting/enable.ts | 51 +- .../spaces_only/tests/alerting/event_log.ts | 26 +- .../tests/alerting/execution_status.ts | 99 +- .../spaces_only/tests/alerting/find.ts | 95 +- .../spaces_only/tests/alerting/get.ts | 81 +- .../alerting/get_alert_instance_summary.ts | 140 +- .../tests/alerting/get_alert_state.ts | 77 +- .../spaces_only/tests/alerting/index.ts | 2 +- .../spaces_only/tests/alerting/migrations.ts | 28 +- .../tests/alerting/mustache_templates.ts | 60 +- .../spaces_only/tests/alerting/mute_all.ts | 38 +- .../tests/alerting/mute_instance.ts | 42 +- .../spaces_only/tests/alerting/notify_when.ts | 48 +- .../{list_alert_types.ts => rule_types.ts} | 64 +- .../spaces_only/tests/alerting/unmute_all.ts | 42 +- .../tests/alerting/unmute_instance.ts | 50 +- .../spaces_only/tests/alerting/update.ts | 104 +- .../tests/alerting/update_api_key.ts | 44 +- .../alert_create_flyout.ts | 9 +- .../apps/triggers_actions_ui/alerts_list.ts | 5 +- .../apps/triggers_actions_ui/details.ts | 24 +- .../apps/triggers_actions_ui/home_page.ts | 2 +- .../lib/get_test_data.ts | 4 +- 207 files changed, 9203 insertions(+), 4485 deletions(-) create mode 100644 docs/api/alerting.asciidoc create mode 100644 docs/api/alerting/create_rule.asciidoc create mode 100644 docs/api/alerting/delete_rule.asciidoc create mode 100644 docs/api/alerting/disable_rule.asciidoc create mode 100644 docs/api/alerting/enable_rule.asciidoc create mode 100644 docs/api/alerting/find_rules.asciidoc create mode 100644 docs/api/alerting/get_rules.asciidoc create mode 100644 docs/api/alerting/health.asciidoc rename docs/api/{alerts => alerting/legacy}/create.asciidoc (97%) rename docs/api/{alerts => alerting/legacy}/delete.asciidoc (86%) rename docs/api/{alerts => alerting/legacy}/disable.asciidoc (85%) rename docs/api/{alerts => alerting/legacy}/enable.asciidoc (85%) rename docs/api/{alerts => alerting/legacy}/find.asciidoc (96%) rename docs/api/{alerts => alerting/legacy}/get.asciidoc (92%) rename docs/api/{alerts => alerting/legacy}/health.asciidoc (92%) create mode 100644 docs/api/alerting/legacy/index.asciidoc rename docs/api/{alerts => alerting/legacy}/list.asciidoc (96%) rename docs/api/{alerts => alerting/legacy}/mute.asciidoc (87%) rename docs/api/{alerts => alerting/legacy}/mute_all.asciidoc (83%) rename docs/api/{alerts => alerting/legacy}/unmute.asciidoc (87%) rename docs/api/{alerts => alerting/legacy}/unmute_all.asciidoc (83%) rename docs/api/{alerts => alerting/legacy}/update.asciidoc (96%) create mode 100644 docs/api/alerting/list_rule_types.asciidoc create mode 100644 docs/api/alerting/mute_alert.asciidoc create mode 100644 docs/api/alerting/mute_all_alerts.asciidoc create mode 100644 docs/api/alerting/unmute_alert.asciidoc create mode 100644 docs/api/alerting/unmute_all_alerts.asciidoc create mode 100644 docs/api/alerting/update_rule.asciidoc delete mode 100644 docs/api/alerts.asciidoc create mode 100644 x-pack/plugins/alerting/server/lib/errors/index.ts create mode 100644 x-pack/plugins/alerting/server/routes/aggregate_rules.test.ts create mode 100644 x-pack/plugins/alerting/server/routes/aggregate_rules.ts create mode 100644 x-pack/plugins/alerting/server/routes/create_rule.test.ts create mode 100644 x-pack/plugins/alerting/server/routes/create_rule.ts create mode 100644 x-pack/plugins/alerting/server/routes/delete_rule.test.ts create mode 100644 x-pack/plugins/alerting/server/routes/delete_rule.ts create mode 100644 x-pack/plugins/alerting/server/routes/disable_rule.test.ts create mode 100644 x-pack/plugins/alerting/server/routes/disable_rule.ts create mode 100644 x-pack/plugins/alerting/server/routes/enable_rule.test.ts create mode 100644 x-pack/plugins/alerting/server/routes/enable_rule.ts create mode 100644 x-pack/plugins/alerting/server/routes/find_rules.test.ts create mode 100644 x-pack/plugins/alerting/server/routes/find_rules.ts create mode 100644 x-pack/plugins/alerting/server/routes/get_rule.test.ts create mode 100644 x-pack/plugins/alerting/server/routes/get_rule.ts create mode 100644 x-pack/plugins/alerting/server/routes/get_rule_alert_summary.test.ts create mode 100644 x-pack/plugins/alerting/server/routes/get_rule_alert_summary.ts create mode 100644 x-pack/plugins/alerting/server/routes/get_rule_state.test.ts create mode 100644 x-pack/plugins/alerting/server/routes/get_rule_state.ts rename x-pack/plugins/alerting/server/routes/{ => legacy}/aggregate.test.ts (91%) rename x-pack/plugins/alerting/server/routes/{ => legacy}/aggregate.ts (83%) rename x-pack/plugins/alerting/server/routes/{ => legacy}/create.test.ts (93%) rename x-pack/plugins/alerting/server/routes/{ => legacy}/create.ts (75%) rename x-pack/plugins/alerting/server/routes/{ => legacy}/delete.test.ts (89%) rename x-pack/plugins/alerting/server/routes/{ => legacy}/delete.ts (76%) rename x-pack/plugins/alerting/server/routes/{ => legacy}/disable.test.ts (86%) rename x-pack/plugins/alerting/server/routes/{ => legacy}/disable.ts (74%) rename x-pack/plugins/alerting/server/routes/{ => legacy}/enable.test.ts (86%) rename x-pack/plugins/alerting/server/routes/{ => legacy}/enable.ts (72%) rename x-pack/plugins/alerting/server/routes/{ => legacy}/find.test.ts (91%) rename x-pack/plugins/alerting/server/routes/{ => legacy}/find.ts (86%) rename x-pack/plugins/alerting/server/routes/{ => legacy}/get.test.ts (90%) rename x-pack/plugins/alerting/server/routes/{ => legacy}/get.ts (76%) rename x-pack/plugins/alerting/server/routes/{ => legacy}/get_alert_instance_summary.test.ts (89%) rename x-pack/plugins/alerting/server/routes/{ => legacy}/get_alert_instance_summary.ts (79%) rename x-pack/plugins/alerting/server/routes/{ => legacy}/get_alert_state.test.ts (93%) rename x-pack/plugins/alerting/server/routes/{ => legacy}/get_alert_state.ts (77%) create mode 100644 x-pack/plugins/alerting/server/routes/legacy/health.test.ts create mode 100644 x-pack/plugins/alerting/server/routes/legacy/health.ts create mode 100644 x-pack/plugins/alerting/server/routes/legacy/index.ts rename x-pack/plugins/alerting/server/routes/{ => legacy}/list_alert_types.test.ts (92%) rename x-pack/plugins/alerting/server/routes/{ => legacy}/list_alert_types.ts (72%) rename x-pack/plugins/alerting/server/routes/{ => legacy}/mute_all.test.ts (86%) rename x-pack/plugins/alerting/server/routes/{ => legacy}/mute_all.ts (74%) rename x-pack/plugins/alerting/server/routes/{ => legacy}/mute_instance.test.ts (87%) rename x-pack/plugins/alerting/server/routes/{ => legacy}/mute_instance.ts (72%) rename x-pack/plugins/alerting/server/routes/{ => legacy}/unmute_all.test.ts (86%) rename x-pack/plugins/alerting/server/routes/{ => legacy}/unmute_all.ts (74%) rename x-pack/plugins/alerting/server/routes/{ => legacy}/unmute_instance.test.ts (87%) rename x-pack/plugins/alerting/server/routes/{ => legacy}/unmute_instance.ts (74%) rename x-pack/plugins/alerting/server/routes/{ => legacy}/update.test.ts (92%) rename x-pack/plugins/alerting/server/routes/{ => legacy}/update.ts (81%) rename x-pack/plugins/alerting/server/routes/{ => legacy}/update_api_key.test.ts (86%) rename x-pack/plugins/alerting/server/routes/{ => legacy}/update_api_key.ts (72%) create mode 100644 x-pack/plugins/alerting/server/routes/lib/index.ts create mode 100644 x-pack/plugins/alerting/server/routes/lib/rewrite_request_case.ts create mode 100644 x-pack/plugins/alerting/server/routes/lib/verify_access_and_context.ts create mode 100644 x-pack/plugins/alerting/server/routes/mute_alert.test.ts create mode 100644 x-pack/plugins/alerting/server/routes/mute_alert.ts create mode 100644 x-pack/plugins/alerting/server/routes/mute_all_rule.test.ts create mode 100644 x-pack/plugins/alerting/server/routes/mute_all_rule.ts create mode 100644 x-pack/plugins/alerting/server/routes/rule_types.test.ts create mode 100644 x-pack/plugins/alerting/server/routes/rule_types.ts create mode 100644 x-pack/plugins/alerting/server/routes/unmute_alert.test.ts create mode 100644 x-pack/plugins/alerting/server/routes/unmute_alert.ts create mode 100644 x-pack/plugins/alerting/server/routes/unmute_all_rule.test.ts create mode 100644 x-pack/plugins/alerting/server/routes/unmute_all_rule.ts create mode 100644 x-pack/plugins/alerting/server/routes/update_rule.test.ts create mode 100644 x-pack/plugins/alerting/server/routes/update_rule.ts create mode 100644 x-pack/plugins/alerting/server/routes/update_rule_api_key.test.ts create mode 100644 x-pack/plugins/alerting/server/routes/update_rule_api_key.ts rename x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/{list_alert_types.ts => rule_types.ts} (66%) rename x-pack/test/alerting_api_integration/spaces_only/tests/alerting/{list_alert_types.ts => rule_types.ts} (59%) diff --git a/api_docs/alerting.json b/api_docs/alerting.json index 5550798c4316f2..f6e37bafdedb09 100644 --- a/api_docs/alerting.json +++ b/api_docs/alerting.json @@ -810,7 +810,7 @@ "description": [], "source": { "path": "x-pack/plugins/alerting/server/alerts_client/alerts_client.ts", - "lineNumber": 132 + "lineNumber": 133 } }, { @@ -821,7 +821,7 @@ "description": [], "source": { "path": "x-pack/plugins/alerting/server/alerts_client/alerts_client.ts", - "lineNumber": 133 + "lineNumber": 134 } }, { @@ -832,7 +832,7 @@ "description": [], "source": { "path": "x-pack/plugins/alerting/server/alerts_client/alerts_client.ts", - "lineNumber": 134 + "lineNumber": 135 } }, { @@ -843,7 +843,7 @@ "description": [], "source": { "path": "x-pack/plugins/alerting/server/alerts_client/alerts_client.ts", - "lineNumber": 135 + "lineNumber": 136 }, "signature": [ "Pick<", @@ -860,7 +860,7 @@ ], "source": { "path": "x-pack/plugins/alerting/server/alerts_client/alerts_client.ts", - "lineNumber": 131 + "lineNumber": 132 }, "initialIsOpen": false }, @@ -905,7 +905,7 @@ "description": [], "source": { "path": "x-pack/plugins/alerting/server/plugin.ts", - "lineNumber": 111 + "lineNumber": 93 } } ], @@ -913,13 +913,13 @@ "returnComment": [], "source": { "path": "x-pack/plugins/alerting/server/plugin.ts", - "lineNumber": 103 + "lineNumber": 85 } } ], "source": { "path": "x-pack/plugins/alerting/server/plugin.ts", - "lineNumber": 102 + "lineNumber": 84 }, "initialIsOpen": false }, @@ -938,7 +938,7 @@ "description": [], "source": { "path": "x-pack/plugins/alerting/server/plugin.ts", - "lineNumber": 123 + "lineNumber": 105 }, "signature": [ "() => Set<", @@ -994,7 +994,7 @@ "description": [], "source": { "path": "x-pack/plugins/alerting/server/plugin.ts", - "lineNumber": 124 + "lineNumber": 106 } } ], @@ -1002,7 +1002,7 @@ "returnComment": [], "source": { "path": "x-pack/plugins/alerting/server/plugin.ts", - "lineNumber": 124 + "lineNumber": 106 } }, { @@ -1013,7 +1013,7 @@ "description": [], "source": { "path": "x-pack/plugins/alerting/server/plugin.ts", - "lineNumber": 125 + "lineNumber": 107 }, "signature": [ "() => Promise<", @@ -1030,7 +1030,7 @@ ], "source": { "path": "x-pack/plugins/alerting/server/plugin.ts", - "lineNumber": 122 + "lineNumber": 104 }, "initialIsOpen": false } @@ -1118,7 +1118,7 @@ }, ", \"enabled\" | \"id\" | \"name\" | \"params\" | \"actions\" | \"muteAll\" | \"tags\" | \"alertTypeId\" | \"consumer\" | \"schedule\" | \"scheduledTaskId\" | \"createdBy\" | \"updatedBy\" | \"createdAt\" | \"updatedAt\" | \"apiKeyOwner\" | \"throttle\" | \"notifyWhen\" | \"mutedInstanceIds\" | \"executionStatus\">>; delete: ({ id }: { id: string; }) => Promise<{}>; create: = never>({ data, options, }: ", "CreateOptions", - ") => Promise<", + ") => Promise>; find: ({ options: { fields, ...options }, }?: { options?: ", + ", \"enabled\" | \"id\" | \"name\" | \"params\" | \"actions\" | \"muteAll\" | \"tags\" | \"alertTypeId\" | \"consumer\" | \"schedule\" | \"scheduledTaskId\" | \"createdBy\" | \"updatedBy\" | \"createdAt\" | \"updatedAt\" | \"apiKeyOwner\" | \"throttle\" | \"notifyWhen\" | \"mutedInstanceIds\" | \"executionStatus\">>; find: ({ options: { fields, ...options }, }?: { options?: ", "FindOptions", " | undefined; }) => Promise<", { @@ -2922,7 +2922,7 @@ "description": [], "source": { "path": "x-pack/plugins/alerting/common/index.ts", - "lineNumber": 28 + "lineNumber": 30 }, "signature": [ "\"alerts\"" @@ -3006,16 +3006,16 @@ }, { "tags": [], - "id": "def-common.BASE_ALERT_API_PATH", + "id": "def-common.BASE_ALERTING_API_PATH", "type": "string", - "label": "BASE_ALERT_API_PATH", + "label": "BASE_ALERTING_API_PATH", "description": [], "source": { "path": "x-pack/plugins/alerting/common/index.ts", - "lineNumber": 27 + "lineNumber": 28 }, "signature": [ - "\"/api/alerts\"" + "\"/api/alerting\"" ], "initialIsOpen": false }, @@ -3034,6 +3034,36 @@ ], "initialIsOpen": false }, + { + "tags": [], + "id": "def-common.INTERNAL_BASE_ALERTING_API_PATH", + "type": "string", + "label": "INTERNAL_BASE_ALERTING_API_PATH", + "description": [], + "source": { + "path": "x-pack/plugins/alerting/common/index.ts", + "lineNumber": 29 + }, + "signature": [ + "\"/internal/alerting\"" + ], + "initialIsOpen": false + }, + { + "tags": [], + "id": "def-common.LEGACY_BASE_ALERT_API_PATH", + "type": "string", + "label": "LEGACY_BASE_ALERT_API_PATH", + "description": [], + "source": { + "path": "x-pack/plugins/alerting/common/index.ts", + "lineNumber": 27 + }, + "signature": [ + "\"/api/alerts\"" + ], + "initialIsOpen": false + }, { "id": "def-common.RawAlertInstance", "type": "Type", diff --git a/api_docs/apm.json b/api_docs/apm.json index ac26de577cf0f9..7ddf4f2548d849 100644 --- a/api_docs/apm.json +++ b/api_docs/apm.json @@ -747,7 +747,7 @@ "text": "APMEventESSearchRequest" }, ">(params: TParams, { includeLegacyData }?: { includeLegacyData?: boolean | undefined; }): Promise<", - "ESSearchResponse", + "InferSearchResponseOf", " void; }" + "{ addProcessorDefinition: (processor: unknown) => void; }" ], "lifecycle": "start", "initialIsOpen": true diff --git a/api_docs/core.json b/api_docs/core.json index e02bd33d6671b4..cb416cba80078d 100644 --- a/api_docs/core.json +++ b/api_docs/core.json @@ -1431,7 +1431,7 @@ "description": [], "source": { "path": "src/core/public/doc_links/doc_links_service.ts", - "lineNumber": 300 + "lineNumber": 302 } }, { @@ -1442,7 +1442,7 @@ "description": [], "source": { "path": "src/core/public/doc_links/doc_links_service.ts", - "lineNumber": 301 + "lineNumber": 303 } }, { @@ -1453,7 +1453,7 @@ "description": [], "source": { "path": "src/core/public/doc_links/doc_links_service.ts", - "lineNumber": 302 + "lineNumber": 304 }, "signature": [ "{ readonly dashboard: { readonly guide: string; readonly drilldowns: string; readonly drilldownsTriggerPicker: string; readonly urlDrilldownTemplateSyntax: string; readonly urlDrilldownVariables: string; }; readonly discover: Record; readonly filebeat: { readonly base: string; readonly installation: string; readonly configuration: string; readonly elasticsearchOutput: string; readonly elasticsearchModule: string; readonly startup: string; readonly exportedFields: string; }; readonly auditbeat: { readonly base: string; }; readonly metricbeat: { readonly base: string; readonly configure: string; readonly httpEndpoint: string; readonly install: string; readonly start: string; }; readonly enterpriseSearch: { readonly base: string; readonly appSearchBase: string; readonly workplaceSearchBase: string; }; readonly heartbeat: { readonly base: string; }; readonly logstash: { readonly base: string; }; readonly functionbeat: { readonly base: string; }; readonly winlogbeat: { readonly base: string; }; readonly aggs: { readonly composite: string; readonly composite_missing_bucket: string; readonly date_histogram: string; readonly date_range: string; readonly date_format_pattern: string; readonly filter: string; readonly filters: string; readonly geohash_grid: string; readonly histogram: string; readonly ip_range: string; readonly range: string; readonly significant_terms: string; readonly terms: string; readonly avg: string; readonly avg_bucket: string; readonly max_bucket: string; readonly min_bucket: string; readonly sum_bucket: string; readonly cardinality: string; readonly count: string; readonly cumulative_sum: string; readonly derivative: string; readonly geo_bounds: string; readonly geo_centroid: string; readonly max: string; readonly median: string; readonly min: string; readonly moving_avg: string; readonly percentile_ranks: string; readonly serial_diff: string; readonly std_dev: string; readonly sum: string; readonly top_hits: string; }; readonly runtimeFields: string; readonly scriptedFields: { readonly scriptFields: string; readonly scriptAggs: string; readonly painless: string; readonly painlessApi: string; readonly painlessLangSpec: string; readonly painlessSyntax: string; readonly painlessWalkthrough: string; readonly luceneExpressions: string; }; readonly indexPatterns: { readonly introduction: string; readonly fieldFormattersNumber: string; readonly fieldFormattersString: string; }; readonly addData: string; readonly kibana: string; readonly elasticsearch: Record; readonly siem: { readonly guide: string; readonly gettingStarted: string; }; readonly query: { readonly eql: string; readonly luceneQuerySyntax: string; readonly queryDsl: string; readonly kueryQuerySyntax: string; }; readonly date: { readonly dateMath: string; readonly dateMathIndexNames: string; }; readonly management: Record; readonly ml: Record; readonly transforms: Record; readonly visualize: Record; readonly apis: Readonly<{ createIndex: string; createSnapshotLifecyclePolicy: string; createRoleMapping: string; createRoleMappingTemplates: string; createApiKey: string; createPipeline: string; createTransformRequest: string; cronExpressions: string; executeWatchActionModes: string; indexExists: string; openIndex: string; putComponentTemplate: string; painlessExecute: string; painlessExecuteAPIContexts: string; putComponentTemplateMetadata: string; putSnapshotLifecyclePolicy: string; putWatch: string; updateTransform: string; }>; readonly observability: Record; readonly alerting: Record; readonly maps: Record; readonly monitoring: Record; readonly security: Readonly<{ apiKeyServiceSettings: string; clusterPrivileges: string; elasticsearchSettings: string; elasticsearchEnableSecurity: string; indicesPrivileges: string; kibanaTLS: string; kibanaPrivileges: string; mappingRoles: string; mappingRolesFieldRules: string; runAsPrivilege: string; }>; readonly watcher: Record; readonly ccs: Record; readonly plugins: Record; readonly snapshotRestore: Record; readonly ingest: Record; }" @@ -1462,7 +1462,7 @@ ], "source": { "path": "src/core/public/doc_links/doc_links_service.ts", - "lineNumber": 299 + "lineNumber": 301 }, "initialIsOpen": false }, @@ -3833,7 +3833,7 @@ ], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 141 + "lineNumber": 142 }, "signature": [ "string | undefined" @@ -3842,7 +3842,7 @@ ], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 139 + "lineNumber": 140 }, "initialIsOpen": false }, @@ -3865,7 +3865,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 78 + "lineNumber": 79 }, "signature": [ "string | string[]" @@ -3879,7 +3879,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 79 + "lineNumber": 80 }, "signature": [ "number | undefined" @@ -3893,7 +3893,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 80 + "lineNumber": 81 }, "signature": [ "number | undefined" @@ -3907,7 +3907,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 81 + "lineNumber": 82 }, "signature": [ "string | undefined" @@ -3916,15 +3916,15 @@ { "tags": [], "id": "def-public.SavedObjectsFindOptions.sortOrder", - "type": "string", + "type": "CompoundType", "label": "sortOrder", "description": [], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 82 + "lineNumber": 83 }, "signature": [ - "string | undefined" + "\"asc\" | \"desc\" | \"_doc\" | undefined" ] }, { @@ -3937,7 +3937,7 @@ ], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 88 + "lineNumber": 89 }, "signature": [ "string[] | undefined" @@ -3953,7 +3953,7 @@ ], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 90 + "lineNumber": 91 }, "signature": [ "string | undefined" @@ -3969,7 +3969,7 @@ ], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 92 + "lineNumber": 93 }, "signature": [ "string[] | undefined" @@ -3985,10 +3985,10 @@ ], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 96 + "lineNumber": 97 }, "signature": [ - "unknown[] | undefined" + "string[] | undefined" ] }, { @@ -4001,7 +4001,7 @@ ], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 101 + "lineNumber": 102 }, "signature": [ "string[] | undefined" @@ -4017,7 +4017,7 @@ ], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 107 + "lineNumber": 108 }, "signature": [ { @@ -4048,7 +4048,7 @@ ], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 111 + "lineNumber": 112 }, "signature": [ "\"AND\" | \"OR\" | undefined" @@ -4064,7 +4064,7 @@ ], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 116 + "lineNumber": 117 }, "signature": [ "\"AND\" | \"OR\" | undefined" @@ -4078,7 +4078,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 117 + "lineNumber": 118 }, "signature": [ "any" @@ -4092,7 +4092,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 118 + "lineNumber": 119 }, "signature": [ "string[] | undefined" @@ -4108,7 +4108,7 @@ ], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 126 + "lineNumber": 127 }, "signature": [ "Map | undefined" @@ -4124,7 +4124,7 @@ ], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 128 + "lineNumber": 129 }, "signature": [ "string | undefined" @@ -4140,7 +4140,7 @@ ], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 132 + "lineNumber": 133 }, "signature": [ { @@ -4156,7 +4156,7 @@ ], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 77 + "lineNumber": 78 }, "initialIsOpen": false }, @@ -4177,7 +4177,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 61 + "lineNumber": 62 } }, { @@ -4188,13 +4188,13 @@ "description": [], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 62 + "lineNumber": 63 } } ], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 60 + "lineNumber": 61 }, "initialIsOpen": false }, @@ -5753,7 +5753,7 @@ ], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 226 + "lineNumber": 227 }, "signature": [ "\"multiple\" | \"single\" | \"multiple-isolated\" | \"agnostic\"" @@ -8002,7 +8002,7 @@ ], "source": { "path": "src/core/server/index.ts", - "lineNumber": 459 + "lineNumber": 462 }, "signature": [ { @@ -8024,7 +8024,7 @@ ], "source": { "path": "src/core/server/index.ts", - "lineNumber": 461 + "lineNumber": 464 }, "signature": [ { @@ -8046,7 +8046,7 @@ ], "source": { "path": "src/core/server/index.ts", - "lineNumber": 463 + "lineNumber": 466 }, "signature": [ { @@ -8068,7 +8068,7 @@ ], "source": { "path": "src/core/server/index.ts", - "lineNumber": 465 + "lineNumber": 468 }, "signature": [ { @@ -8099,7 +8099,7 @@ ], "source": { "path": "src/core/server/index.ts", - "lineNumber": 470 + "lineNumber": 473 }, "signature": [ { @@ -8121,7 +8121,7 @@ ], "source": { "path": "src/core/server/index.ts", - "lineNumber": 472 + "lineNumber": 475 }, "signature": [ { @@ -8143,7 +8143,7 @@ ], "source": { "path": "src/core/server/index.ts", - "lineNumber": 474 + "lineNumber": 477 }, "signature": [ { @@ -8165,7 +8165,7 @@ ], "source": { "path": "src/core/server/index.ts", - "lineNumber": 476 + "lineNumber": 479 }, "signature": [ { @@ -8187,7 +8187,7 @@ ], "source": { "path": "src/core/server/index.ts", - "lineNumber": 478 + "lineNumber": 481 }, "signature": [ { @@ -8209,7 +8209,7 @@ ], "source": { "path": "src/core/server/index.ts", - "lineNumber": 480 + "lineNumber": 483 }, "signature": [ { @@ -8231,7 +8231,7 @@ ], "source": { "path": "src/core/server/index.ts", - "lineNumber": 482 + "lineNumber": 485 }, "signature": [ { @@ -8247,7 +8247,7 @@ ], "source": { "path": "src/core/server/index.ts", - "lineNumber": 457 + "lineNumber": 460 }, "initialIsOpen": false }, @@ -8272,7 +8272,7 @@ ], "source": { "path": "src/core/server/index.ts", - "lineNumber": 505 + "lineNumber": 508 }, "signature": [ { @@ -8294,7 +8294,7 @@ ], "source": { "path": "src/core/server/index.ts", - "lineNumber": 507 + "lineNumber": 510 }, "signature": [ { @@ -8316,7 +8316,7 @@ ], "source": { "path": "src/core/server/index.ts", - "lineNumber": 509 + "lineNumber": 512 }, "signature": [ { @@ -8338,7 +8338,7 @@ ], "source": { "path": "src/core/server/index.ts", - "lineNumber": 511 + "lineNumber": 514 }, "signature": [ { @@ -8360,7 +8360,7 @@ ], "source": { "path": "src/core/server/index.ts", - "lineNumber": 513 + "lineNumber": 516 }, "signature": [ { @@ -8382,7 +8382,7 @@ ], "source": { "path": "src/core/server/index.ts", - "lineNumber": 515 + "lineNumber": 518 }, "signature": [ { @@ -8397,7 +8397,7 @@ ], "source": { "path": "src/core/server/index.ts", - "lineNumber": 503 + "lineNumber": 506 }, "initialIsOpen": false }, @@ -14272,7 +14272,7 @@ "description": [], "source": { "path": "src/core/server/index.ts", - "lineNumber": 425 + "lineNumber": 428 }, "signature": [ "{ savedObjects: { client: Pick<", @@ -14283,7 +14283,7 @@ "section": "def-server.SavedObjectsClient", "text": "SavedObjectsClient" }, - ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"errors\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\">; typeRegistry: Pick<", + ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"errors\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\" | \"createPointInTimeFinder\">; typeRegistry: Pick<", { "pluginId": "core", "scope": "server", @@ -14307,7 +14307,7 @@ "section": "def-server.SavedObjectsClient", "text": "SavedObjectsClient" }, - ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"errors\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\">; getExporter: (client: Pick<", + ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"errors\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\" | \"createPointInTimeFinder\">; getExporter: (client: Pick<", { "pluginId": "core", "scope": "server", @@ -14320,7 +14320,7 @@ ], "source": { "path": "src/core/server/index.ts", - "lineNumber": 424 + "lineNumber": 427 }, "initialIsOpen": false }, @@ -15689,7 +15689,7 @@ "section": "def-server.SavedObjectsClient", "text": "SavedObjectsClient" }, - ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"errors\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\">) => ", + ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"errors\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\" | \"createPointInTimeFinder\">) => ", { "pluginId": "core", "scope": "server", @@ -15715,7 +15715,7 @@ "section": "def-server.SavedObjectsClient", "text": "SavedObjectsClient" }, - ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"errors\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\">" + ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"errors\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\" | \"createPointInTimeFinder\">" ], "description": [], "source": { @@ -15965,7 +15965,7 @@ "lineNumber": 22 }, "signature": [ - "Pick & { transport: { request(params: TransportRequestParams, options?: TransportRequestOptions | undefined): TransportRequestPromise; }; }" + "Pick & { transport: { request(params: TransportRequestParams, options?: TransportRequestOptions | undefined): TransportRequestPromise; }; }" ], "initialIsOpen": false }, @@ -16616,7 +16616,7 @@ ], "source": { "path": "src/core/server/index.ts", - "lineNumber": 493 + "lineNumber": 496 }, "signature": [ "() => Promise<[", diff --git a/api_docs/core_http.json b/api_docs/core_http.json index 8053550cc0e805..ce5ceb2840ec70 100644 --- a/api_docs/core_http.json +++ b/api_docs/core_http.json @@ -974,7 +974,7 @@ "lineNumber": 197 }, "signature": [ - "\"error\" | \"manual\" | \"follow\" | undefined" + "\"error\" | \"follow\" | \"manual\" | undefined" ] }, { diff --git a/api_docs/core_saved_objects.json b/api_docs/core_saved_objects.json index d862df7ef10bbc..54f13f3911be6b 100644 --- a/api_docs/core_saved_objects.json +++ b/api_docs/core_saved_objects.json @@ -1530,7 +1530,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 397 + "lineNumber": 402 }, "signature": [ "typeof ", @@ -1551,7 +1551,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 398 + "lineNumber": 403 }, "signature": [ "typeof ", @@ -1601,7 +1601,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 414 + "lineNumber": 419 } }, { @@ -1614,7 +1614,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 414 + "lineNumber": 419 } }, { @@ -1634,7 +1634,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 414 + "lineNumber": 419 } } ], @@ -1642,7 +1642,7 @@ "returnComment": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 414 + "lineNumber": 419 } }, { @@ -1697,7 +1697,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 425 + "lineNumber": 430 } }, { @@ -1717,7 +1717,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 426 + "lineNumber": 431 } } ], @@ -1725,7 +1725,7 @@ "returnComment": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 424 + "lineNumber": 429 } }, { @@ -1780,7 +1780,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 439 + "lineNumber": 444 } }, { @@ -1799,7 +1799,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 440 + "lineNumber": 445 } } ], @@ -1807,7 +1807,7 @@ "returnComment": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 438 + "lineNumber": 443 } }, { @@ -1839,7 +1839,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 452 + "lineNumber": 457 } }, { @@ -1852,7 +1852,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 452 + "lineNumber": 457 } }, { @@ -1871,7 +1871,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 452 + "lineNumber": 457 } } ], @@ -1879,7 +1879,7 @@ "returnComment": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 452 + "lineNumber": 457 } }, { @@ -1925,7 +1925,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 461 + "lineNumber": 466 } } ], @@ -1933,7 +1933,7 @@ "returnComment": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 461 + "lineNumber": 466 } }, { @@ -1990,7 +1990,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 477 + "lineNumber": 482 } }, { @@ -2009,7 +2009,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 478 + "lineNumber": 483 } } ], @@ -2017,7 +2017,7 @@ "returnComment": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 476 + "lineNumber": 481 } }, { @@ -2059,7 +2059,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 491 + "lineNumber": 496 } }, { @@ -2074,7 +2074,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 492 + "lineNumber": 497 } }, { @@ -2093,7 +2093,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 493 + "lineNumber": 498 } } ], @@ -2101,7 +2101,7 @@ "returnComment": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 490 + "lineNumber": 495 } }, { @@ -2143,7 +2143,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 506 + "lineNumber": 511 } }, { @@ -2158,7 +2158,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 507 + "lineNumber": 512 } }, { @@ -2177,7 +2177,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 508 + "lineNumber": 513 } } ], @@ -2185,7 +2185,7 @@ "returnComment": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 505 + "lineNumber": 510 } }, { @@ -2225,7 +2225,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 521 + "lineNumber": 526 } }, { @@ -2238,7 +2238,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 522 + "lineNumber": 527 } }, { @@ -2251,7 +2251,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 523 + "lineNumber": 528 } }, { @@ -2270,7 +2270,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 524 + "lineNumber": 529 } } ], @@ -2278,7 +2278,7 @@ "returnComment": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 520 + "lineNumber": 525 } }, { @@ -2318,7 +2318,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 538 + "lineNumber": 543 } }, { @@ -2331,7 +2331,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 539 + "lineNumber": 544 } }, { @@ -2344,7 +2344,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 540 + "lineNumber": 545 } }, { @@ -2363,7 +2363,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 541 + "lineNumber": 546 } } ], @@ -2371,7 +2371,7 @@ "returnComment": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 537 + "lineNumber": 542 } }, { @@ -2411,7 +2411,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 555 + "lineNumber": 560 } }, { @@ -2424,7 +2424,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 556 + "lineNumber": 561 } }, { @@ -2437,7 +2437,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 557 + "lineNumber": 562 } }, { @@ -2456,7 +2456,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 558 + "lineNumber": 563 } } ], @@ -2464,7 +2464,7 @@ "returnComment": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 554 + "lineNumber": 559 } }, { @@ -2519,7 +2519,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 569 + "lineNumber": 574 } }, { @@ -2539,7 +2539,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 570 + "lineNumber": 575 } } ], @@ -2547,7 +2547,7 @@ "returnComment": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 568 + "lineNumber": 573 } }, { @@ -2587,7 +2587,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 579 + "lineNumber": 584 } }, { @@ -2600,7 +2600,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 580 + "lineNumber": 585 } }, { @@ -2620,7 +2620,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 581 + "lineNumber": 586 } } ], @@ -2628,7 +2628,7 @@ "returnComment": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 578 + "lineNumber": 583 } }, { @@ -2655,7 +2655,7 @@ ">" ], "description": [ - "\nOpens a Point In Time (PIT) against the indices for the specified Saved Object types.\nThe returned `id` can then be passed to {@link SavedObjectsClient.find} to search\nagainst that PIT." + "\nOpens a Point In Time (PIT) against the indices for the specified Saved Object types.\nThe returned `id` can then be passed to {@link SavedObjectsClient.find} to search\nagainst that PIT.\n\nOnly use this API if you have an advanced use case that's not solved by the\n{@link SavedObjectsClient.createPointInTimeFinder} method." ], "children": [ { @@ -2668,7 +2668,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 592 + "lineNumber": 600 } }, { @@ -2687,7 +2687,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 593 + "lineNumber": 601 } } ], @@ -2695,7 +2695,7 @@ "returnComment": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 591 + "lineNumber": 599 } }, { @@ -2722,7 +2722,7 @@ ">" ], "description": [ - "\nCloses a Point In Time (PIT) by ID. This simply proxies the request to ES via the\nElasticsearch client, and is included in the Saved Objects Client as a convenience\nfor consumers who are using {@link SavedObjectsClient.openPointInTimeForType}." + "\nCloses a Point In Time (PIT) by ID. This simply proxies the request to ES via the\nElasticsearch client, and is included in the Saved Objects Client as a convenience\nfor consumers who are using {@link SavedObjectsClient.openPointInTimeForType}.\n\nOnly use this API if you have an advanced use case that's not solved by the\n{@link SavedObjectsClient.createPointInTimeFinder} method." ], "children": [ { @@ -2735,7 +2735,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 603 + "lineNumber": 614 } }, { @@ -2755,7 +2755,90 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 603 + "lineNumber": 614 + } + } + ], + "tags": [], + "returnComment": [], + "source": { + "path": "src/core/server/saved_objects/service/saved_objects_client.ts", + "lineNumber": 614 + } + }, + { + "id": "def-server.SavedObjectsClient.createPointInTimeFinder", + "type": "Function", + "label": "createPointInTimeFinder", + "signature": [ + "(findOptions: Pick<", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreSavedObjectsPluginApi", + "section": "def-server.SavedObjectsFindOptions", + "text": "SavedObjectsFindOptions" + }, + ", \"type\" | \"filter\" | \"fields\" | \"search\" | \"perPage\" | \"sortField\" | \"sortOrder\" | \"searchFields\" | \"rootSearchFields\" | \"hasReference\" | \"hasReferenceOperator\" | \"defaultSearchOperator\" | \"namespaces\" | \"typeToNamespacesMap\" | \"preference\">, dependencies?: ", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreSavedObjectsPluginApi", + "section": "def-server.SavedObjectsCreatePointInTimeFinderDependencies", + "text": "SavedObjectsCreatePointInTimeFinderDependencies" + }, + " | undefined) => ", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreSavedObjectsPluginApi", + "section": "def-server.ISavedObjectsPointInTimeFinder", + "text": "ISavedObjectsPointInTimeFinder" + } + ], + "description": [ + "\nReturns a {@link ISavedObjectsPointInTimeFinder} to help page through\nlarge sets of saved objects. We strongly recommend using this API for\nany `find` queries that might return more than 1000 saved objects,\nhowever this API is only intended for use in server-side \"batch\"\nprocessing of objects where you are collecting all objects in memory\nor streaming them back to the client.\n\nDo NOT use this API in a route handler to facilitate paging through\nsaved objects on the client-side unless you are streaming all of the\nresults back to the client at once. Because the returned generator is\nstateful, you cannot rely on subsequent http requests retrieving new\npages from the same Kibana server in multi-instance deployments.\n\nThe generator wraps calls to {@link SavedObjectsClient.find} and iterates\nover multiple pages of results using `_pit` and `search_after`. This will\nopen a new Point-In-Time (PIT), and continue paging until a set of\nresults is received that's smaller than the designated `perPage`.\n\nOnce you have retrieved all of the results you need, it is recommended\nto call `close()` to clean up the PIT and prevent Elasticsearch from\nconsuming resources unnecessarily. This is only required if you are\ndone iterating and have not yet paged through all of the results: the\nPIT will automatically be closed for you once you reach the last page\nof results, or if the underlying call to `find` fails for any reason.\n" + ], + "children": [ + { + "type": "Object", + "label": "findOptions", + "isRequired": true, + "signature": [ + "Pick<", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreSavedObjectsPluginApi", + "section": "def-server.SavedObjectsFindOptions", + "text": "SavedObjectsFindOptions" + }, + ", \"type\" | \"filter\" | \"fields\" | \"search\" | \"perPage\" | \"sortField\" | \"sortOrder\" | \"searchFields\" | \"rootSearchFields\" | \"hasReference\" | \"hasReferenceOperator\" | \"defaultSearchOperator\" | \"namespaces\" | \"typeToNamespacesMap\" | \"preference\">" + ], + "description": [], + "source": { + "path": "src/core/server/saved_objects/service/saved_objects_client.ts", + "lineNumber": 664 + } + }, + { + "type": "Object", + "label": "dependencies", + "isRequired": false, + "signature": [ + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreSavedObjectsPluginApi", + "section": "def-server.SavedObjectsCreatePointInTimeFinderDependencies", + "text": "SavedObjectsCreatePointInTimeFinderDependencies" + }, + " | undefined" + ], + "description": [], + "source": { + "path": "src/core/server/saved_objects/service/saved_objects_client.ts", + "lineNumber": 665 } } ], @@ -2763,13 +2846,13 @@ "returnComment": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 603 + "lineNumber": 663 } } ], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 396 + "lineNumber": 401 }, "initialIsOpen": false }, @@ -4167,7 +4250,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/export/saved_objects_exporter.ts", - "lineNumber": 38 + "lineNumber": 37 }, "signature": [ "Pick<", @@ -4178,7 +4261,7 @@ "section": "def-server.SavedObjectsClient", "text": "SavedObjectsClient" }, - ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"errors\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\">" + ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"errors\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\" | \"createPointInTimeFinder\">" ] }, { @@ -4189,7 +4272,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/export/saved_objects_exporter.ts", - "lineNumber": 39 + "lineNumber": 38 }, "signature": [ "Record" + ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"errors\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\" | \"createPointInTimeFinder\">" ] }, { @@ -4274,7 +4357,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/export/saved_objects_exporter.ts", - "lineNumber": 50 + "lineNumber": 49 }, "signature": [ "Pick<", @@ -4296,7 +4379,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/export/saved_objects_exporter.ts", - "lineNumber": 51 + "lineNumber": 50 } }, { @@ -4307,7 +4390,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/export/saved_objects_exporter.ts", - "lineNumber": 52 + "lineNumber": 51 }, "signature": [ "Logger" @@ -4316,7 +4399,7 @@ ], "source": { "path": "src/core/server/saved_objects/export/saved_objects_exporter.ts", - "lineNumber": 48 + "lineNumber": 47 } } ], @@ -4324,7 +4407,7 @@ "returnComment": [], "source": { "path": "src/core/server/saved_objects/export/saved_objects_exporter.ts", - "lineNumber": 43 + "lineNumber": 42 } }, { @@ -4364,7 +4447,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/export/saved_objects_exporter.ts", - "lineNumber": 75 + "lineNumber": 74 } } ], @@ -4374,7 +4457,7 @@ "returnComment": [], "source": { "path": "src/core/server/saved_objects/export/saved_objects_exporter.ts", - "lineNumber": 75 + "lineNumber": 74 } }, { @@ -4414,7 +4497,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/export/saved_objects_exporter.ts", - "lineNumber": 93 + "lineNumber": 92 } } ], @@ -4424,13 +4507,13 @@ "returnComment": [], "source": { "path": "src/core/server/saved_objects/export/saved_objects_exporter.ts", - "lineNumber": 93 + "lineNumber": 92 } } ], "source": { "path": "src/core/server/saved_objects/export/saved_objects_exporter.ts", - "lineNumber": 37 + "lineNumber": 36 }, "initialIsOpen": false }, @@ -4727,7 +4810,7 @@ "section": "def-server.SavedObjectsClient", "text": "SavedObjectsClient" }, - ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"errors\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\">" + ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"errors\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\" | \"createPointInTimeFinder\">" ] }, { @@ -4820,7 +4903,7 @@ "section": "def-server.SavedObjectsClient", "text": "SavedObjectsClient" }, - ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"errors\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\">" + ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"errors\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\" | \"createPointInTimeFinder\">" ] }, { @@ -5258,7 +5341,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 237 + "lineNumber": 257 } }, { @@ -5271,7 +5354,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 238 + "lineNumber": 258 } }, { @@ -5290,7 +5373,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 239 + "lineNumber": 259 } } ], @@ -5302,7 +5385,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 236 + "lineNumber": 256 } }, { @@ -5359,7 +5442,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 330 + "lineNumber": 350 } }, { @@ -5378,7 +5461,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 331 + "lineNumber": 351 } } ], @@ -5390,7 +5473,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 329 + "lineNumber": 349 } }, { @@ -5445,7 +5528,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 517 + "lineNumber": 541 } }, { @@ -5464,7 +5547,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 518 + "lineNumber": 542 } } ], @@ -5472,7 +5555,7 @@ "returnComment": [], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 516 + "lineNumber": 540 } }, { @@ -5504,7 +5587,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 602 + "lineNumber": 627 } }, { @@ -5517,7 +5600,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 602 + "lineNumber": 627 } }, { @@ -5536,7 +5619,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 602 + "lineNumber": 627 } } ], @@ -5546,7 +5629,7 @@ "returnComment": [], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 602 + "lineNumber": 627 } }, { @@ -5578,7 +5661,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 664 + "lineNumber": 690 } }, { @@ -5597,7 +5680,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 665 + "lineNumber": 691 } } ], @@ -5607,7 +5690,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 663 + "lineNumber": 689 } }, { @@ -5651,7 +5734,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 725 + "lineNumber": 751 } } ], @@ -5663,7 +5746,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 725 + "lineNumber": 751 } }, { @@ -5720,7 +5803,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 875 + "lineNumber": 906 } }, { @@ -5739,7 +5822,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 876 + "lineNumber": 907 } } ], @@ -5751,7 +5834,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 874 + "lineNumber": 905 } }, { @@ -5791,7 +5874,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 960 + "lineNumber": 993 } }, { @@ -5804,7 +5887,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 961 + "lineNumber": 994 } }, { @@ -5823,7 +5906,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 962 + "lineNumber": 995 } } ], @@ -5835,7 +5918,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 959 + "lineNumber": 992 } }, { @@ -5875,7 +5958,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 998 + "lineNumber": 1035 } }, { @@ -5888,7 +5971,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 999 + "lineNumber": 1036 } }, { @@ -5907,7 +5990,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 1000 + "lineNumber": 1037 } } ], @@ -5919,7 +6002,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 997 + "lineNumber": 1034 } }, { @@ -5959,7 +6042,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 1117 + "lineNumber": 1160 } }, { @@ -5972,7 +6055,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 1118 + "lineNumber": 1161 } }, { @@ -5985,7 +6068,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 1119 + "lineNumber": 1162 } }, { @@ -6004,7 +6087,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 1120 + "lineNumber": 1163 } } ], @@ -6014,7 +6097,7 @@ "returnComment": [], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 1116 + "lineNumber": 1159 } }, { @@ -6054,7 +6137,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 1190 + "lineNumber": 1232 } }, { @@ -6067,7 +6150,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 1191 + "lineNumber": 1233 } }, { @@ -6080,7 +6163,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 1192 + "lineNumber": 1234 } }, { @@ -6099,7 +6182,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 1193 + "lineNumber": 1235 } } ], @@ -6107,7 +6190,7 @@ "returnComment": [], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 1189 + "lineNumber": 1231 } }, { @@ -6147,7 +6230,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 1253 + "lineNumber": 1295 } }, { @@ -6160,7 +6243,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 1254 + "lineNumber": 1296 } }, { @@ -6173,7 +6256,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 1255 + "lineNumber": 1297 } }, { @@ -6192,7 +6275,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 1256 + "lineNumber": 1298 } } ], @@ -6200,7 +6283,7 @@ "returnComment": [], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 1252 + "lineNumber": 1294 } }, { @@ -6257,7 +6340,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 1358 + "lineNumber": 1401 } }, { @@ -6276,7 +6359,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 1359 + "lineNumber": 1402 } } ], @@ -6288,7 +6371,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 1357 + "lineNumber": 1400 } }, { @@ -6328,7 +6411,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 1573 + "lineNumber": 1620 } }, { @@ -6341,7 +6424,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 1574 + "lineNumber": 1621 } }, { @@ -6360,7 +6443,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 1575 + "lineNumber": 1622 } } ], @@ -6368,7 +6451,7 @@ "returnComment": [], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 1572 + "lineNumber": 1619 } }, { @@ -6392,7 +6475,7 @@ "section": "def-server.SavedObjectsIncrementCounterOptions", "text": "SavedObjectsIncrementCounterOptions" }, - ") => Promise<", + ") => Promise<", { "pluginId": "core", "scope": "common", @@ -6418,7 +6501,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 1670 + "lineNumber": 1731 } }, { @@ -6433,7 +6516,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 1671 + "lineNumber": 1732 } }, { @@ -6456,7 +6539,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 1672 + "lineNumber": 1733 } }, { @@ -6470,14 +6553,15 @@ "docId": "kibCoreSavedObjectsPluginApi", "section": "def-server.SavedObjectsIncrementCounterOptions", "text": "SavedObjectsIncrementCounterOptions" - } + }, + "" ], "description": [ "- {@link SavedObjectsIncrementCounterOptions}" ], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 1673 + "lineNumber": 1734 } } ], @@ -6487,7 +6571,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 1669 + "lineNumber": 1730 } }, { @@ -6514,7 +6598,7 @@ ">" ], "description": [ - "\nOpens a Point In Time (PIT) against the indices for the specified Saved Object types.\nThe returned `id` can then be passed to `SavedObjects.find` to search against that PIT.\n" + "\nOpens a Point In Time (PIT) against the indices for the specified Saved Object types.\nThe returned `id` can then be passed to `SavedObjects.find` to search against that PIT.\n\nOnly use this API if you have an advanced use case that's not solved by the\n{@link SavedObjectsRepository.createPointInTimeFinder} method.\n" ], "children": [ { @@ -6527,7 +6611,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 1821 + "lineNumber": 1891 } }, { @@ -6546,7 +6630,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 1822 + "lineNumber": 1892 } } ], @@ -6558,7 +6642,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 1820 + "lineNumber": 1890 } }, { @@ -6585,7 +6669,7 @@ ">" ], "description": [ - "\nCloses a Point In Time (PIT) by ID. This simply proxies the request to ES\nvia the Elasticsearch client, and is included in the Saved Objects Client\nas a convenience for consumers who are using `openPointInTimeForType`.\n" + "\nCloses a Point In Time (PIT) by ID. This simply proxies the request to ES\nvia the Elasticsearch client, and is included in the Saved Objects Client\nas a convenience for consumers who are using `openPointInTimeForType`.\n\nOnly use this API if you have an advanced use case that's not solved by the\n{@link SavedObjectsRepository.createPointInTimeFinder} method.\n" ], "children": [ { @@ -6598,7 +6682,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 1890 + "lineNumber": 1967 } }, { @@ -6620,7 +6704,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 1891 + "lineNumber": 1968 } } ], @@ -6630,13 +6714,96 @@ ], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 1889 + "lineNumber": 1966 + } + }, + { + "id": "def-server.SavedObjectsRepository.createPointInTimeFinder", + "type": "Function", + "label": "createPointInTimeFinder", + "signature": [ + "(findOptions: Pick<", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreSavedObjectsPluginApi", + "section": "def-server.SavedObjectsFindOptions", + "text": "SavedObjectsFindOptions" + }, + ", \"type\" | \"filter\" | \"fields\" | \"search\" | \"perPage\" | \"sortField\" | \"sortOrder\" | \"searchFields\" | \"rootSearchFields\" | \"hasReference\" | \"hasReferenceOperator\" | \"defaultSearchOperator\" | \"namespaces\" | \"typeToNamespacesMap\" | \"preference\">, dependencies?: ", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreSavedObjectsPluginApi", + "section": "def-server.SavedObjectsCreatePointInTimeFinderDependencies", + "text": "SavedObjectsCreatePointInTimeFinderDependencies" + }, + " | undefined) => ", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreSavedObjectsPluginApi", + "section": "def-server.ISavedObjectsPointInTimeFinder", + "text": "ISavedObjectsPointInTimeFinder" + } + ], + "description": [ + "\nReturns a {@link ISavedObjectsPointInTimeFinder} to help page through\nlarge sets of saved objects. We strongly recommend using this API for\nany `find` queries that might return more than 1000 saved objects,\nhowever this API is only intended for use in server-side \"batch\"\nprocessing of objects where you are collecting all objects in memory\nor streaming them back to the client.\n\nDo NOT use this API in a route handler to facilitate paging through\nsaved objects on the client-side unless you are streaming all of the\nresults back to the client at once. Because the returned generator is\nstateful, you cannot rely on subsequent http requests retrieving new\npages from the same Kibana server in multi-instance deployments.\n\nThis generator wraps calls to {@link SavedObjectsRepository.find} and\niterates over multiple pages of results using `_pit` and `search_after`.\nThis will open a new Point-In-Time (PIT), and continue paging until a\nset of results is received that's smaller than the designated `perPage`.\n\nOnce you have retrieved all of the results you need, it is recommended\nto call `close()` to clean up the PIT and prevent Elasticsearch from\nconsuming resources unnecessarily. This is only required if you are\ndone iterating and have not yet paged through all of the results: the\nPIT will automatically be closed for you once you reach the last page\nof results, or if the underlying call to `find` fails for any reason.\n" + ], + "children": [ + { + "type": "Object", + "label": "findOptions", + "isRequired": true, + "signature": [ + "Pick<", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreSavedObjectsPluginApi", + "section": "def-server.SavedObjectsFindOptions", + "text": "SavedObjectsFindOptions" + }, + ", \"type\" | \"filter\" | \"fields\" | \"search\" | \"perPage\" | \"sortField\" | \"sortOrder\" | \"searchFields\" | \"rootSearchFields\" | \"hasReference\" | \"hasReferenceOperator\" | \"defaultSearchOperator\" | \"namespaces\" | \"typeToNamespacesMap\" | \"preference\">" + ], + "description": [], + "source": { + "path": "src/core/server/saved_objects/service/lib/repository.ts", + "lineNumber": 2023 + } + }, + { + "type": "Object", + "label": "dependencies", + "isRequired": false, + "signature": [ + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreSavedObjectsPluginApi", + "section": "def-server.SavedObjectsCreatePointInTimeFinderDependencies", + "text": "SavedObjectsCreatePointInTimeFinderDependencies" + }, + " | undefined" + ], + "description": [], + "source": { + "path": "src/core/server/saved_objects/service/lib/repository.ts", + "lineNumber": 2024 + } + } + ], + "tags": [], + "returnComment": [], + "source": { + "path": "src/core/server/saved_objects/service/lib/repository.ts", + "lineNumber": 2022 } } ], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 143 + "lineNumber": 158 }, "initialIsOpen": false }, @@ -7617,6 +7784,62 @@ ], "functions": [], "interfaces": [ + { + "id": "def-server.ISavedObjectsPointInTimeFinder", + "type": "Interface", + "label": "ISavedObjectsPointInTimeFinder", + "description": [], + "tags": [ + "public" + ], + "children": [ + { + "tags": [], + "id": "def-server.ISavedObjectsPointInTimeFinder.find", + "type": "Function", + "label": "find", + "description": [ + "\nAn async generator which wraps calls to `savedObjectsClient.find` and\niterates over multiple pages of results using `_pit` and `search_after`.\nThis will open a new Point-In-Time (PIT), and continue paging until a set\nof results is received that's smaller than the designated `perPage` size." + ], + "source": { + "path": "src/core/server/saved_objects/service/lib/point_in_time_finder.ts", + "lineNumber": 49 + }, + "signature": [ + "() => AsyncGenerator<", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreSavedObjectsPluginApi", + "section": "def-server.SavedObjectsFindResponse", + "text": "SavedObjectsFindResponse" + }, + ", any, unknown>" + ] + }, + { + "tags": [], + "id": "def-server.ISavedObjectsPointInTimeFinder.close", + "type": "Function", + "label": "close", + "description": [ + "\nCloses the Point-In-Time associated with this finder instance.\n\nOnce you have retrieved all of the results you need, it is recommended\nto call `close()` to clean up the PIT and prevent Elasticsearch from\nconsuming resources unnecessarily. This is only required if you are\ndone iterating and have not yet paged through all of the results: the\nPIT will automatically be closed for you once you reach the last page\nof results, or if the underlying call to `find` fails for any reason." + ], + "source": { + "path": "src/core/server/saved_objects/service/lib/point_in_time_finder.ts", + "lineNumber": 60 + }, + "signature": [ + "() => Promise" + ] + } + ], + "source": { + "path": "src/core/server/saved_objects/service/lib/point_in_time_finder.ts", + "lineNumber": 42 + }, + "initialIsOpen": false + }, { "id": "def-server.SavedObjectExportBaseOptions", "type": "Interface", @@ -7843,7 +8066,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 219 + "lineNumber": 224 }, "signature": [ "string | undefined" @@ -7859,7 +8082,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 221 + "lineNumber": 226 }, "signature": [ "boolean | \"wait_for\" | undefined" @@ -7868,7 +8091,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 217 + "lineNumber": 222 }, "initialIsOpen": false }, @@ -7893,7 +8116,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 230 + "lineNumber": 235 }, "signature": [ "string[]" @@ -7902,7 +8125,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 228 + "lineNumber": 233 }, "initialIsOpen": false }, @@ -7927,7 +8150,7 @@ ], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 141 + "lineNumber": 142 }, "signature": [ "string | undefined" @@ -7936,7 +8159,7 @@ ], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 139 + "lineNumber": 140 }, "initialIsOpen": false }, @@ -7969,7 +8192,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 66 + "lineNumber": 71 }, "signature": [ "string | undefined" @@ -7983,7 +8206,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 67 + "lineNumber": 72 } }, { @@ -7994,7 +8217,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 68 + "lineNumber": 73 }, "signature": [ "T" @@ -8008,7 +8231,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 69 + "lineNumber": 74 }, "signature": [ "string | undefined" @@ -8022,7 +8245,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 70 + "lineNumber": 75 }, "signature": [ { @@ -8045,7 +8268,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 72 + "lineNumber": 77 }, "signature": [ { @@ -8068,7 +8291,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 82 + "lineNumber": 87 }, "signature": [ "string | undefined" @@ -8084,7 +8307,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 84 + "lineNumber": 89 }, "signature": [ "string | undefined" @@ -8100,7 +8323,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 91 + "lineNumber": 96 }, "signature": [ "string[] | undefined" @@ -8109,7 +8332,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 65 + "lineNumber": 70 }, "initialIsOpen": false }, @@ -8132,7 +8355,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 294 + "lineNumber": 299 } }, { @@ -8143,7 +8366,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 295 + "lineNumber": 300 } }, { @@ -8156,7 +8379,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 297 + "lineNumber": 302 }, "signature": [ "string[] | undefined" @@ -8165,7 +8388,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 293 + "lineNumber": 298 }, "initialIsOpen": false }, @@ -8198,7 +8421,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 120 + "lineNumber": 125 }, "signature": [ { @@ -8214,7 +8437,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 119 + "lineNumber": 124 }, "initialIsOpen": false }, @@ -8247,7 +8470,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 305 + "lineNumber": 310 }, "signature": [ { @@ -8263,7 +8486,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 304 + "lineNumber": 309 }, "initialIsOpen": false }, @@ -8306,7 +8529,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 101 + "lineNumber": 106 } }, { @@ -8319,7 +8542,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 103 + "lineNumber": 108 } }, { @@ -8332,7 +8555,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 105 + "lineNumber": 110 }, "signature": [ "Partial" @@ -8348,7 +8571,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 112 + "lineNumber": 117 }, "signature": [ "string | undefined" @@ -8357,7 +8580,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 98 + "lineNumber": 103 }, "initialIsOpen": false }, @@ -8399,7 +8622,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 275 + "lineNumber": 280 }, "signature": [ "boolean | \"wait_for\" | undefined" @@ -8408,7 +8631,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 273 + "lineNumber": 278 }, "initialIsOpen": false }, @@ -8441,7 +8664,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 313 + "lineNumber": 318 }, "signature": [ { @@ -8457,7 +8680,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 312 + "lineNumber": 317 }, "initialIsOpen": false }, @@ -8480,7 +8703,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 184 + "lineNumber": 189 } }, { @@ -8491,13 +8714,13 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 185 + "lineNumber": 190 } } ], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 183 + "lineNumber": 188 }, "initialIsOpen": false }, @@ -8520,7 +8743,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 193 + "lineNumber": 198 }, "signature": [ "{ id: string; type: string; error: ", @@ -8537,7 +8760,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 192 + "lineNumber": 197 }, "initialIsOpen": false }, @@ -8617,7 +8840,7 @@ "section": "def-server.SavedObjectsClient", "text": "SavedObjectsClient" }, - ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"errors\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\">" + ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"errors\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\" | \"createPointInTimeFinder\">" ] }, { @@ -8689,7 +8912,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 385 + "lineNumber": 390 } }, { @@ -8702,13 +8925,13 @@ ], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 389 + "lineNumber": 394 } } ], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 380 + "lineNumber": 385 }, "initialIsOpen": false }, @@ -8931,7 +9154,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 27 + "lineNumber": 32 }, "signature": [ "string | undefined" @@ -8947,7 +9170,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 29 + "lineNumber": 34 }, "signature": [ "boolean | undefined" @@ -8963,7 +9186,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 34 + "lineNumber": 39 }, "signature": [ "string | undefined" @@ -8979,7 +9202,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 36 + "lineNumber": 41 }, "signature": [ { @@ -9002,7 +9225,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 46 + "lineNumber": 51 }, "signature": [ "string | undefined" @@ -9016,7 +9239,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 47 + "lineNumber": 52 }, "signature": [ { @@ -9039,7 +9262,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 49 + "lineNumber": 54 }, "signature": [ "boolean | \"wait_for\" | undefined" @@ -9055,7 +9278,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 51 + "lineNumber": 56 }, "signature": [ "string | undefined" @@ -9071,7 +9294,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 58 + "lineNumber": 63 }, "signature": [ "string[] | undefined" @@ -9080,7 +9303,45 @@ ], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 25 + "lineNumber": 30 + }, + "initialIsOpen": false + }, + { + "id": "def-server.SavedObjectsCreatePointInTimeFinderDependencies", + "type": "Interface", + "label": "SavedObjectsCreatePointInTimeFinderDependencies", + "description": [], + "tags": [ + "public" + ], + "children": [ + { + "tags": [], + "id": "def-server.SavedObjectsCreatePointInTimeFinderDependencies.client", + "type": "Object", + "label": "client", + "description": [], + "source": { + "path": "src/core/server/saved_objects/service/lib/point_in_time_finder.ts", + "lineNumber": 30 + }, + "signature": [ + "Pick, \"find\" | \"openPointInTimeForType\" | \"closePointInTime\">" + ] + } + ], + "source": { + "path": "src/core/server/saved_objects/service/lib/point_in_time_finder.ts", + "lineNumber": 29 }, "initialIsOpen": false }, @@ -9122,7 +9383,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 118 + "lineNumber": 133 }, "signature": [ "boolean | undefined" @@ -9131,7 +9392,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 116 + "lineNumber": 131 }, "initialIsOpen": false }, @@ -9173,7 +9434,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 239 + "lineNumber": 244 }, "signature": [ "boolean | \"wait_for\" | undefined" @@ -9182,7 +9443,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 237 + "lineNumber": 242 }, "initialIsOpen": false }, @@ -9207,7 +9468,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 248 + "lineNumber": 253 }, "signature": [ "string[]" @@ -9216,7 +9477,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 246 + "lineNumber": 251 }, "initialIsOpen": false }, @@ -9258,7 +9519,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 284 + "lineNumber": 289 }, "signature": [ "boolean | \"wait_for\" | undefined" @@ -9274,7 +9535,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 286 + "lineNumber": 291 }, "signature": [ "boolean | undefined" @@ -9283,7 +9544,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 282 + "lineNumber": 287 }, "initialIsOpen": false }, @@ -9548,7 +9809,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 78 + "lineNumber": 79 }, "signature": [ "string | string[]" @@ -9562,7 +9823,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 79 + "lineNumber": 80 }, "signature": [ "number | undefined" @@ -9576,7 +9837,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 80 + "lineNumber": 81 }, "signature": [ "number | undefined" @@ -9590,7 +9851,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 81 + "lineNumber": 82 }, "signature": [ "string | undefined" @@ -9599,15 +9860,15 @@ { "tags": [], "id": "def-server.SavedObjectsFindOptions.sortOrder", - "type": "string", + "type": "CompoundType", "label": "sortOrder", "description": [], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 82 + "lineNumber": 83 }, "signature": [ - "string | undefined" + "\"asc\" | \"desc\" | \"_doc\" | undefined" ] }, { @@ -9620,7 +9881,7 @@ ], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 88 + "lineNumber": 89 }, "signature": [ "string[] | undefined" @@ -9636,7 +9897,7 @@ ], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 90 + "lineNumber": 91 }, "signature": [ "string | undefined" @@ -9652,7 +9913,7 @@ ], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 92 + "lineNumber": 93 }, "signature": [ "string[] | undefined" @@ -9668,10 +9929,10 @@ ], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 96 + "lineNumber": 97 }, "signature": [ - "unknown[] | undefined" + "string[] | undefined" ] }, { @@ -9684,7 +9945,7 @@ ], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 101 + "lineNumber": 102 }, "signature": [ "string[] | undefined" @@ -9700,7 +9961,7 @@ ], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 107 + "lineNumber": 108 }, "signature": [ { @@ -9731,7 +9992,7 @@ ], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 111 + "lineNumber": 112 }, "signature": [ "\"AND\" | \"OR\" | undefined" @@ -9747,7 +10008,7 @@ ], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 116 + "lineNumber": 117 }, "signature": [ "\"AND\" | \"OR\" | undefined" @@ -9761,7 +10022,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 117 + "lineNumber": 118 }, "signature": [ "any" @@ -9775,7 +10036,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 118 + "lineNumber": 119 }, "signature": [ "string[] | undefined" @@ -9791,7 +10052,7 @@ ], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 126 + "lineNumber": 127 }, "signature": [ "Map | undefined" @@ -9807,7 +10068,7 @@ ], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 128 + "lineNumber": 129 }, "signature": [ "string | undefined" @@ -9823,7 +10084,7 @@ ], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 132 + "lineNumber": 133 }, "signature": [ { @@ -9839,7 +10100,7 @@ ], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 77 + "lineNumber": 78 }, "initialIsOpen": false }, @@ -9860,7 +10121,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 61 + "lineNumber": 62 } }, { @@ -9871,13 +10132,13 @@ "description": [], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 62 + "lineNumber": 63 } } ], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 60 + "lineNumber": 61 }, "initialIsOpen": false }, @@ -9910,7 +10171,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 172 + "lineNumber": 177 }, "signature": [ { @@ -9931,7 +10192,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 173 + "lineNumber": 178 } }, { @@ -9942,7 +10203,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 174 + "lineNumber": 179 } }, { @@ -9953,7 +10214,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 175 + "lineNumber": 180 } }, { @@ -9964,7 +10225,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 176 + "lineNumber": 181 }, "signature": [ "string | undefined" @@ -9973,7 +10234,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 171 + "lineNumber": 176 }, "initialIsOpen": false }, @@ -10010,7 +10271,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 131 + "lineNumber": 136 } }, { @@ -10023,16 +10284,16 @@ ], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 160 + "lineNumber": 165 }, "signature": [ - "unknown[] | undefined" + "string[] | undefined" ] } ], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 127 + "lineNumber": 132 }, "initialIsOpen": false }, @@ -10971,7 +11232,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 135 + "lineNumber": 150 } }, { @@ -10984,7 +11245,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 137 + "lineNumber": 152 }, "signature": [ "number | undefined" @@ -10993,7 +11254,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 133 + "lineNumber": 148 }, "initialIsOpen": false }, @@ -11009,7 +11270,7 @@ "section": "def-server.SavedObjectsIncrementCounterOptions", "text": "SavedObjectsIncrementCounterOptions" }, - " extends ", + " extends ", { "pluginId": "core", "scope": "server", @@ -11033,7 +11294,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 102 + "lineNumber": 113 }, "signature": [ "boolean | undefined" @@ -11049,7 +11310,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 104 + "lineNumber": 115 }, "signature": [ { @@ -11072,16 +11333,32 @@ ], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 109 + "lineNumber": 120 }, "signature": [ "boolean | \"wait_for\" | undefined" ] + }, + { + "tags": [], + "id": "def-server.SavedObjectsIncrementCounterOptions.upsertAttributes", + "type": "Uncategorized", + "label": "upsertAttributes", + "description": [ + "\nAttributes to use when upserting the document if it doesn't exist." + ], + "source": { + "path": "src/core/server/saved_objects/service/lib/repository.ts", + "lineNumber": 124 + }, + "signature": [ + "Attributes | undefined" + ] } ], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 97 + "lineNumber": 107 }, "initialIsOpen": false }, @@ -11243,7 +11520,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 355 + "lineNumber": 360 }, "signature": [ "string | undefined" @@ -11259,7 +11536,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 359 + "lineNumber": 364 }, "signature": [ "string | undefined" @@ -11268,7 +11545,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 351 + "lineNumber": 356 }, "initialIsOpen": false }, @@ -11291,13 +11568,13 @@ ], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 369 + "lineNumber": 374 } } ], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 365 + "lineNumber": 370 }, "initialIsOpen": false }, @@ -11318,7 +11595,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 69 + "lineNumber": 70 } }, { @@ -11329,7 +11606,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 70 + "lineNumber": 71 }, "signature": [ "string | undefined" @@ -11338,7 +11615,7 @@ ], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 68 + "lineNumber": 69 }, "initialIsOpen": false }, @@ -11491,7 +11768,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 257 + "lineNumber": 262 }, "signature": [ "boolean | undefined" @@ -11500,7 +11777,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 255 + "lineNumber": 260 }, "initialIsOpen": false }, @@ -11542,13 +11819,13 @@ ], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 266 + "lineNumber": 271 } } ], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 264 + "lineNumber": 269 }, "initialIsOpen": false }, @@ -11592,7 +11869,7 @@ "section": "def-server.SavedObjectsRepository", "text": "SavedObjectsRepository" }, - ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\" | \"deleteByNamespace\" | \"incrementCounter\">" + ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\" | \"createPointInTimeFinder\" | \"deleteByNamespace\" | \"incrementCounter\">" ] }, { @@ -11616,7 +11893,7 @@ "section": "def-server.SavedObjectsRepository", "text": "SavedObjectsRepository" }, - ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\" | \"deleteByNamespace\" | \"incrementCounter\">" + ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\" | \"createPointInTimeFinder\" | \"deleteByNamespace\" | \"incrementCounter\">" ] } ], @@ -11741,7 +12018,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 331 + "lineNumber": 336 }, "signature": [ { @@ -11764,7 +12041,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 341 + "lineNumber": 346 }, "signature": [ "\"conflict\" | \"exactMatch\" | \"aliasMatch\"" @@ -11780,7 +12057,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 345 + "lineNumber": 350 }, "signature": [ "string | undefined" @@ -11789,7 +12066,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 330 + "lineNumber": 335 }, "initialIsOpen": false }, @@ -11931,7 +12208,7 @@ "section": "def-server.SavedObjectsClient", "text": "SavedObjectsClient" }, - ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"errors\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\">" + ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"errors\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\" | \"createPointInTimeFinder\">" ] }, { @@ -11963,7 +12240,7 @@ "section": "def-server.SavedObjectsRepository", "text": "SavedObjectsRepository" }, - ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\" | \"deleteByNamespace\" | \"incrementCounter\">" + ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\" | \"createPointInTimeFinder\" | \"deleteByNamespace\" | \"incrementCounter\">" ] }, { @@ -11987,7 +12264,7 @@ "section": "def-server.SavedObjectsRepository", "text": "SavedObjectsRepository" }, - ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\" | \"deleteByNamespace\" | \"incrementCounter\">" + ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\" | \"createPointInTimeFinder\" | \"deleteByNamespace\" | \"incrementCounter\">" ] }, { @@ -12034,7 +12311,7 @@ "section": "def-server.SavedObjectsClient", "text": "SavedObjectsClient" }, - ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"errors\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\">) => Pick<", + ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"errors\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\" | \"createPointInTimeFinder\">) => Pick<", { "pluginId": "core", "scope": "server", @@ -12066,7 +12343,7 @@ "section": "def-server.SavedObjectsClient", "text": "SavedObjectsClient" }, - ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"errors\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\">) => Pick<", + ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"errors\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\" | \"createPointInTimeFinder\">) => Pick<", { "pluginId": "core", "scope": "server", @@ -12127,7 +12404,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 50 + "lineNumber": 51 }, "signature": [ "{ [status: string]: number; skipped: number; migrated: number; }" @@ -12136,7 +12413,7 @@ ], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 49 + "lineNumber": 50 }, "initialIsOpen": false }, @@ -12159,7 +12436,7 @@ ], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 237 + "lineNumber": 238 } }, { @@ -12172,7 +12449,7 @@ ], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 244 + "lineNumber": 245 } }, { @@ -12185,7 +12462,7 @@ ], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 248 + "lineNumber": 249 }, "signature": [ { @@ -12207,7 +12484,7 @@ ], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 252 + "lineNumber": 253 }, "signature": [ "string | undefined" @@ -12223,7 +12500,7 @@ ], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 256 + "lineNumber": 257 }, "signature": [ "string | undefined" @@ -12239,7 +12516,7 @@ ], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 260 + "lineNumber": 261 }, "signature": [ { @@ -12261,7 +12538,7 @@ ], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 264 + "lineNumber": 265 }, "signature": [ { @@ -12292,7 +12569,7 @@ ], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 313 + "lineNumber": 314 }, "signature": [ "string | undefined" @@ -12308,7 +12585,7 @@ ], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 317 + "lineNumber": 318 }, "signature": [ { @@ -12324,7 +12601,7 @@ ], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 233 + "lineNumber": 234 }, "initialIsOpen": false }, @@ -12349,7 +12626,7 @@ ], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 329 + "lineNumber": 330 }, "signature": [ "boolean | undefined" @@ -12365,7 +12642,7 @@ ], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 333 + "lineNumber": 334 }, "signature": [ "string | undefined" @@ -12381,7 +12658,7 @@ ], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 338 + "lineNumber": 339 }, "signature": [ "string | undefined" @@ -12397,7 +12674,7 @@ ], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 343 + "lineNumber": 344 }, "signature": [ "((savedObject: ", @@ -12421,7 +12698,7 @@ ], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 348 + "lineNumber": 349 }, "signature": [ "((savedObject: ", @@ -12445,7 +12722,7 @@ ], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 357 + "lineNumber": 358 }, "signature": [ "((savedObject: ", @@ -12469,7 +12746,7 @@ ], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 368 + "lineNumber": 369 }, "signature": [ { @@ -12492,7 +12769,7 @@ ], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 411 + "lineNumber": 412 }, "signature": [ { @@ -12508,7 +12785,7 @@ ], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 325 + "lineNumber": 326 }, "initialIsOpen": false }, @@ -12606,7 +12883,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 206 + "lineNumber": 211 }, "signature": [ "string | undefined" @@ -12622,7 +12899,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 208 + "lineNumber": 213 }, "signature": [ { @@ -12645,7 +12922,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 210 + "lineNumber": 215 }, "signature": [ "boolean | \"wait_for\" | undefined" @@ -12654,7 +12931,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 204 + "lineNumber": 209 }, "initialIsOpen": false }, @@ -12689,7 +12966,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 322 + "lineNumber": 327 }, "signature": [ "Partial" @@ -12703,7 +12980,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 323 + "lineNumber": 328 }, "signature": [ { @@ -12719,7 +12996,7 @@ ], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 320 + "lineNumber": 325 }, "initialIsOpen": false } @@ -12736,7 +13013,7 @@ "description": [], "source": { "path": "src/core/server/saved_objects/export/saved_objects_exporter.ts", - "lineNumber": 32 + "lineNumber": 31 }, "signature": [ "{ exportByTypes: (options: SavedObjectsExportByTypeOptions) => Promise<", @@ -12776,10 +13053,10 @@ ], "source": { "path": "src/core/server/saved_objects/service/lib/repository.ts", - "lineNumber": 128 + "lineNumber": 143 }, "signature": [ - "{ get: (type: string, id: string, options?: SavedObjectsBaseOptions) => Promise>; delete: (type: string, id: string, options?: SavedObjectsDeleteOptions) => Promise<{}>; create: (type: string, attributes: T, options?: SavedObjectsCreateOptions) => Promise>; find: (options: SavedObjectsFindOptions) => Promise>; update: (type: string, id: string, attributes: Partial, options?: SavedObjectsUpdateOptions) => Promise>; bulkCreate: (objects: SavedObjectsBulkCreateObject[], options?: SavedObjectsCreateOptions) => Promise>; bulkGet: (objects?: SavedObjectsBulkGetObject[], options?: SavedObjectsBaseOptions) => Promise>; bulkUpdate: (objects: SavedObjectsBulkUpdateObject[], options?: SavedObjectsBulkUpdateOptions) => Promise>; checkConflicts: (objects?: SavedObjectsCheckConflictsObject[], options?: SavedObjectsBaseOptions) => Promise; resolve: (type: string, id: string, options?: SavedObjectsBaseOptions) => Promise>; addToNamespaces: (type: string, id: string, namespaces: string[], options?: SavedObjectsAddToNamespacesOptions) => Promise; deleteFromNamespaces: (type: string, id: string, namespaces: string[], options?: SavedObjectsDeleteFromNamespacesOptions) => Promise; removeReferencesTo: (type: string, id: string, options?: SavedObjectsRemoveReferencesToOptions) => Promise; openPointInTimeForType: (type: string | string[], { keepAlive, preference }?: SavedObjectsOpenPointInTimeOptions) => Promise; closePointInTime: (id: string, options?: SavedObjectsBaseOptions | undefined) => Promise; deleteByNamespace: (namespace: string, options?: SavedObjectsDeleteByNamespaceOptions) => Promise; incrementCounter: (type: string, id: string, counterFields: (string | SavedObjectsIncrementCounterField)[], options?: SavedObjectsIncrementCounterOptions) => Promise>; }" + "{ get: (type: string, id: string, options?: SavedObjectsBaseOptions) => Promise>; delete: (type: string, id: string, options?: SavedObjectsDeleteOptions) => Promise<{}>; create: (type: string, attributes: T, options?: SavedObjectsCreateOptions) => Promise>; find: (options: SavedObjectsFindOptions) => Promise>; update: (type: string, id: string, attributes: Partial, options?: SavedObjectsUpdateOptions) => Promise>; bulkCreate: (objects: SavedObjectsBulkCreateObject[], options?: SavedObjectsCreateOptions) => Promise>; bulkGet: (objects?: SavedObjectsBulkGetObject[], options?: SavedObjectsBaseOptions) => Promise>; bulkUpdate: (objects: SavedObjectsBulkUpdateObject[], options?: SavedObjectsBulkUpdateOptions) => Promise>; checkConflicts: (objects?: SavedObjectsCheckConflictsObject[], options?: SavedObjectsBaseOptions) => Promise; resolve: (type: string, id: string, options?: SavedObjectsBaseOptions) => Promise>; addToNamespaces: (type: string, id: string, namespaces: string[], options?: SavedObjectsAddToNamespacesOptions) => Promise; deleteFromNamespaces: (type: string, id: string, namespaces: string[], options?: SavedObjectsDeleteFromNamespacesOptions) => Promise; removeReferencesTo: (type: string, id: string, options?: SavedObjectsRemoveReferencesToOptions) => Promise; openPointInTimeForType: (type: string | string[], { keepAlive, preference }?: SavedObjectsOpenPointInTimeOptions) => Promise; closePointInTime: (id: string, options?: SavedObjectsBaseOptions | undefined) => Promise; createPointInTimeFinder: (findOptions: Pick, dependencies?: SavedObjectsCreatePointInTimeFinderDependencies | undefined) => ISavedObjectsPointInTimeFinder; deleteByNamespace: (namespace: string, options?: SavedObjectsDeleteByNamespaceOptions) => Promise; incrementCounter: (type: string, id: string, counterFields: (string | SavedObjectsIncrementCounterField)[], options?: SavedObjectsIncrementCounterOptions) => Promise>; }" ], "initialIsOpen": false }, @@ -12814,7 +13091,7 @@ ], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 148 + "lineNumber": 149 }, "signature": [ "false | true | \"wait_for\"" @@ -12895,7 +13172,7 @@ ], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 212 + "lineNumber": 213 }, "signature": [ "{ get: (type: string, id: string, options?: SavedObjectsBaseOptions) => Promise>; delete: (type: string, id: string, options?: ", @@ -12972,7 +13249,7 @@ "section": "def-server.SavedObjectsClient", "text": "SavedObjectsClient" }, - ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"errors\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\">" + ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"errors\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\" | \"createPointInTimeFinder\">" ], "initialIsOpen": false }, @@ -13041,7 +13318,7 @@ "section": "def-server.SavedObjectsClient", "text": "SavedObjectsClient" }, - ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"errors\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\">" + ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"errors\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\" | \"createPointInTimeFinder\">" ], "initialIsOpen": false }, @@ -13055,13 +13332,46 @@ "description": [], "source": { "path": "src/core/server/saved_objects/service/saved_objects_client.ts", - "lineNumber": 375 + "lineNumber": 380 }, "signature": [ "SavedObjectsBaseOptions" ], "initialIsOpen": false }, + { + "id": "def-server.SavedObjectsCreatePointInTimeFinderOptions", + "type": "Type", + "label": "SavedObjectsCreatePointInTimeFinderOptions", + "tags": [ + "public" + ], + "description": [], + "source": { + "path": "src/core/server/saved_objects/service/lib/point_in_time_finder.ts", + "lineNumber": 21 + }, + "signature": [ + "{ type: string | string[]; filter?: any; fields?: string[] | undefined; search?: string | undefined; perPage?: number | undefined; sortField?: string | undefined; sortOrder?: \"asc\" | \"desc\" | \"_doc\" | undefined; searchFields?: string[] | undefined; rootSearchFields?: string[] | undefined; hasReference?: ", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreSavedObjectsPluginApi", + "section": "def-server.SavedObjectsFindOptionsReference", + "text": "SavedObjectsFindOptionsReference" + }, + " | ", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreSavedObjectsPluginApi", + "section": "def-server.SavedObjectsFindOptionsReference", + "text": "SavedObjectsFindOptionsReference" + }, + "[] | undefined; hasReferenceOperator?: \"AND\" | \"OR\" | undefined; defaultSearchOperator?: \"AND\" | \"OR\" | undefined; namespaces?: string[] | undefined; typeToNamespacesMap?: Map | undefined; preference?: string | undefined; }" + ], + "initialIsOpen": false + }, { "id": "def-server.SavedObjectsExportTransform", "type": "Type", @@ -13210,7 +13520,7 @@ ], "source": { "path": "src/core/server/saved_objects/types.ts", - "lineNumber": 226 + "lineNumber": 227 }, "signature": [ "\"multiple\" | \"single\" | \"multiple-isolated\" | \"agnostic\"" diff --git a/api_docs/data.json b/api_docs/data.json index a9ef03d881ce88..a51ad465fe9034 100644 --- a/api_docs/data.json +++ b/api_docs/data.json @@ -7022,8 +7022,7 @@ "docId": "kibDataSearchPluginApi", "section": "def-common.ISearchRequestParams", "text": "ISearchRequestParams" - }, - ">" + } ], "description": [], "children": [ @@ -8696,7 +8695,7 @@ "section": "def-common.ISearchRequestParams", "text": "ISearchRequestParams" }, - ">>" + ">" ], "description": [], "tags": [], @@ -8709,7 +8708,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/es_search/types.ts", - "lineNumber": 20 + "lineNumber": 19 }, "signature": [ "string | undefined" @@ -8718,7 +8717,7 @@ ], "source": { "path": "src/plugins/data/common/search/es_search/types.ts", - "lineNumber": 19 + "lineNumber": 18 }, "initialIsOpen": false }, @@ -10763,7 +10762,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/search_source/fetch/types.ts", - "lineNumber": 37 + "lineNumber": 40 } }, { @@ -10774,7 +10773,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/search_source/fetch/types.ts", - "lineNumber": 38 + "lineNumber": 41 } }, { @@ -10785,7 +10784,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/search_source/fetch/types.ts", - "lineNumber": 39 + "lineNumber": 42 } }, { @@ -10796,7 +10795,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/search_source/fetch/types.ts", - "lineNumber": 40 + "lineNumber": 43 } }, { @@ -10807,7 +10806,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/search_source/fetch/types.ts", - "lineNumber": 41 + "lineNumber": 44 } }, { @@ -10818,13 +10817,13 @@ "description": [], "source": { "path": "src/plugins/data/common/search/search_source/fetch/types.ts", - "lineNumber": 42 + "lineNumber": 45 } } ], "source": { "path": "src/plugins/data/common/search/search_source/fetch/types.ts", - "lineNumber": 36 + "lineNumber": 39 }, "initialIsOpen": false }, @@ -11484,7 +11483,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/es_search/types.ts", - "lineNumber": 13 + "lineNumber": 12 }, "signature": [ "\"es\"" @@ -11608,7 +11607,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 26 + "lineNumber": 34 }, "signature": [ "ExpressionFunctionDefinition<\"kibana_context\", ", @@ -11751,10 +11750,10 @@ "description": [], "source": { "path": "src/plugins/data/common/search/es_search/types.ts", - "lineNumber": 23 + "lineNumber": 22 }, "signature": [ - "IKibanaSearchResponse>" + "IKibanaSearchResponse>" ], "initialIsOpen": false }, @@ -11847,7 +11846,7 @@ "description": [], "source": { "path": "src/plugins/data/common/index_patterns/expressions/load_index_pattern.ts", - "lineNumber": 34 + "lineNumber": 35 }, "signature": [ "ExpressionFunctionDefinition<\"indexPatternLoad\", null, Arguments, Output, ", @@ -14487,15 +14486,7 @@ "lineNumber": 46 }, "signature": [ - "{ addQuerySuggestionProvider: (language: string, provider: ", - { - "pluginId": "data", - "scope": "public", - "docId": "kibDataAutocompletePluginApi", - "section": "def-public.QuerySuggestionGetFn", - "text": "QuerySuggestionGetFn" - }, - ") => void; getQuerySuggestions: ", + "{ getQuerySuggestions: ", { "pluginId": "data", "scope": "public", @@ -15163,7 +15154,7 @@ "section": "def-server.SavedObjectsClient", "text": "SavedObjectsClient" }, - ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"errors\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\">, elasticsearchClient: ", + ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"errors\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\" | \"createPointInTimeFinder\">, elasticsearchClient: ", { "pluginId": "core", "scope": "server", @@ -15190,7 +15181,7 @@ "description": [], "source": { "path": "src/plugins/data/server/plugin.ts", - "lineNumber": 107 + "lineNumber": 104 } } ], @@ -15198,7 +15189,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/server/plugin.ts", - "lineNumber": 107 + "lineNumber": 104 } }, { @@ -15214,7 +15205,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/server/plugin.ts", - "lineNumber": 121 + "lineNumber": 118 } } ], @@ -19507,7 +19498,7 @@ "section": "def-common.ISearchRequestParams", "text": "ISearchRequestParams" }, - ">>" + ">" ], "description": [], "tags": [], @@ -19520,7 +19511,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/es_search/types.ts", - "lineNumber": 20 + "lineNumber": 19 }, "signature": [ "string | undefined" @@ -19529,7 +19520,7 @@ ], "source": { "path": "src/plugins/data/common/search/es_search/types.ts", - "lineNumber": 19 + "lineNumber": 18 }, "initialIsOpen": false }, @@ -20433,7 +20424,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/es_search/types.ts", - "lineNumber": 13 + "lineNumber": 12 }, "signature": [ "\"es\"" @@ -20527,7 +20518,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 26 + "lineNumber": 34 }, "signature": [ "ExpressionFunctionDefinition<\"kibana_context\", ", @@ -20636,10 +20627,10 @@ "description": [], "source": { "path": "src/plugins/data/common/search/es_search/types.ts", - "lineNumber": 23 + "lineNumber": 22 }, "signature": [ - "IKibanaSearchResponse>" + "IKibanaSearchResponse>" ], "initialIsOpen": false }, @@ -20717,7 +20708,7 @@ "description": [], "source": { "path": "src/plugins/data/common/index_patterns/expressions/load_index_pattern.ts", - "lineNumber": 34 + "lineNumber": 35 }, "signature": [ "ExpressionFunctionDefinition<\"indexPatternLoad\", null, Arguments, Output, ", diff --git a/api_docs/data_autocomplete.json b/api_docs/data_autocomplete.json index a66ca49d0c1819..7792a7abe5c8a1 100644 --- a/api_docs/data_autocomplete.json +++ b/api_docs/data_autocomplete.json @@ -324,7 +324,7 @@ "description": [], "source": { "path": "src/plugins/data/public/autocomplete/autocomplete_service.ts", - "lineNumber": 93 + "lineNumber": 96 }, "signature": [ "{ getQuerySuggestions: QuerySuggestionGetFn; hasQuerySuggestions: (language: string) => boolean; getValueSuggestions: ValueSuggestionsGetFn; }" diff --git a/api_docs/data_enhanced.json b/api_docs/data_enhanced.json index 5bd7a970f9b73a..80f1d1fc15a5a8 100644 --- a/api_docs/data_enhanced.json +++ b/api_docs/data_enhanced.json @@ -46,7 +46,7 @@ "description": [], "source": { "path": "x-pack/plugins/data_enhanced/public/plugin.ts", - "lineNumber": 40 + "lineNumber": 38 }, "signature": [ "void" @@ -903,9 +903,7 @@ "lineNumber": 27 }, "signature": [ - "IKibanaSearchResponse>" + "IKibanaSearchResponse>" ], "initialIsOpen": false }, diff --git a/api_docs/data_index_patterns.json b/api_docs/data_index_patterns.json index afcea7d50b3040..8058f6a72f9c34 100644 --- a/api_docs/data_index_patterns.json +++ b/api_docs/data_index_patterns.json @@ -368,7 +368,7 @@ "section": "def-server.DataPluginStart", "text": "DataPluginStart" }, - ">, { logger, expressions }: ", + ">, { expressions }: ", { "pluginId": "data", "scope": "server", @@ -413,12 +413,12 @@ "description": [], "source": { "path": "src/plugins/data/server/index_patterns/index_patterns_service.ts", - "lineNumber": 49 + "lineNumber": 47 } }, { "type": "Object", - "label": "{ logger, expressions }", + "label": "{ expressions }", "isRequired": true, "signature": [ { @@ -432,7 +432,7 @@ "description": [], "source": { "path": "src/plugins/data/server/index_patterns/index_patterns_service.ts", - "lineNumber": 50 + "lineNumber": 48 } } ], @@ -440,7 +440,7 @@ "returnComment": [], "source": { "path": "src/plugins/data/server/index_patterns/index_patterns_service.ts", - "lineNumber": 48 + "lineNumber": 46 } }, { @@ -472,7 +472,7 @@ "section": "def-server.SavedObjectsClient", "text": "SavedObjectsClient" }, - ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"errors\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\">, elasticsearchClient: ", + ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"errors\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\" | \"createPointInTimeFinder\">, elasticsearchClient: ", { "pluginId": "core", "scope": "server", @@ -507,7 +507,7 @@ "description": [], "source": { "path": "src/plugins/data/server/index_patterns/index_patterns_service.ts", - "lineNumber": 76 + "lineNumber": 58 } }, { @@ -526,7 +526,7 @@ "description": [], "source": { "path": "src/plugins/data/server/index_patterns/index_patterns_service.ts", - "lineNumber": 76 + "lineNumber": 58 } } ], @@ -534,13 +534,13 @@ "returnComment": [], "source": { "path": "src/plugins/data/server/index_patterns/index_patterns_service.ts", - "lineNumber": 76 + "lineNumber": 58 } } ], "source": { "path": "src/plugins/data/server/index_patterns/index_patterns_service.ts", - "lineNumber": 47 + "lineNumber": 45 }, "initialIsOpen": false } @@ -3903,7 +3903,7 @@ "label": "getIndexPatternLoadMeta", "source": { "path": "src/plugins/data/common/index_patterns/expressions/load_index_pattern.ts", - "lineNumber": 41 + "lineNumber": 42 }, "tags": [], "returnComment": [], @@ -5937,7 +5937,7 @@ "description": [], "source": { "path": "src/plugins/data/common/index_patterns/expressions/load_index_pattern.ts", - "lineNumber": 18 + "lineNumber": 19 }, "signature": [ "\"index_pattern\"" @@ -5951,7 +5951,7 @@ "description": [], "source": { "path": "src/plugins/data/common/index_patterns/expressions/load_index_pattern.ts", - "lineNumber": 19 + "lineNumber": 20 }, "signature": [ { @@ -5966,7 +5966,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/expressions/load_index_pattern.ts", - "lineNumber": 17 + "lineNumber": 18 }, "initialIsOpen": false }, @@ -6677,7 +6677,7 @@ "description": [], "source": { "path": "src/plugins/data/common/index_patterns/expressions/load_index_pattern.ts", - "lineNumber": 34 + "lineNumber": 35 }, "signature": [ "ExpressionFunctionDefinition<\"indexPatternLoad\", null, Arguments, Output, ", diff --git a/api_docs/data_search.json b/api_docs/data_search.json index a75b669cbd288d..53b8294de02fc6 100644 --- a/api_docs/data_search.json +++ b/api_docs/data_search.json @@ -1621,7 +1621,7 @@ "description": [], "source": { "path": "src/plugins/data/server/search/types.ts", - "lineNumber": 88 + "lineNumber": 90 }, "signature": [ "(sessionId: string, attributes: Partial) => Promise<", @@ -1643,7 +1643,7 @@ "description": [], "source": { "path": "src/plugins/data/server/search/types.ts", - "lineNumber": 89 + "lineNumber": 91 }, "signature": [ "(sessionId: string) => Promise<", @@ -1665,7 +1665,7 @@ "description": [], "source": { "path": "src/plugins/data/server/search/types.ts", - "lineNumber": 90 + "lineNumber": 92 }, "signature": [ "(options: Pick<", @@ -1695,7 +1695,7 @@ "description": [], "source": { "path": "src/plugins/data/server/search/types.ts", - "lineNumber": 91 + "lineNumber": 93 }, "signature": [ "(sessionId: string, attributes: Partial) => Promise<", @@ -1717,7 +1717,7 @@ "description": [], "source": { "path": "src/plugins/data/server/search/types.ts", - "lineNumber": 92 + "lineNumber": 94 }, "signature": [ "(sessionId: string) => Promise<{}>" @@ -1731,7 +1731,7 @@ "description": [], "source": { "path": "src/plugins/data/server/search/types.ts", - "lineNumber": 93 + "lineNumber": 95 }, "signature": [ "(sessionId: string) => Promise<{}>" @@ -1745,7 +1745,7 @@ "description": [], "source": { "path": "src/plugins/data/server/search/types.ts", - "lineNumber": 94 + "lineNumber": 96 }, "signature": [ "(sessionId: string, expires: Date) => Promise<", @@ -1762,7 +1762,7 @@ ], "source": { "path": "src/plugins/data/server/search/types.ts", - "lineNumber": 87 + "lineNumber": 89 }, "initialIsOpen": false }, @@ -1843,7 +1843,7 @@ "description": [], "source": { "path": "src/plugins/data/server/search/types.ts", - "lineNumber": 41 + "lineNumber": 43 }, "signature": [ { @@ -1865,7 +1865,7 @@ ], "source": { "path": "src/plugins/data/server/search/types.ts", - "lineNumber": 46 + "lineNumber": 48 }, "signature": [ " ", @@ -2009,7 +2009,7 @@ "description": [], "source": { "path": "src/plugins/data/server/search/types.ts", - "lineNumber": 110 + "lineNumber": 112 }, "signature": [ "(request: ", @@ -2038,7 +2038,7 @@ "description": [], "source": { "path": "src/plugins/data/server/search/types.ts", - "lineNumber": 111 + "lineNumber": 113 }, "signature": [ "{ asScoped: (request: ", @@ -2063,7 +2063,7 @@ ], "source": { "path": "src/plugins/data/server/search/types.ts", - "lineNumber": 97 + "lineNumber": 99 }, "initialIsOpen": false }, @@ -2094,7 +2094,7 @@ "description": [], "source": { "path": "src/plugins/data/server/search/types.ts", - "lineNumber": 73 + "lineNumber": 75 }, "signature": [ "(request: SearchStrategyRequest, options: ", @@ -2126,7 +2126,7 @@ "description": [], "source": { "path": "src/plugins/data/server/search/types.ts", - "lineNumber": 78 + "lineNumber": 80 }, "signature": [ "((id: string, options: ", @@ -2156,7 +2156,7 @@ "description": [], "source": { "path": "src/plugins/data/server/search/types.ts", - "lineNumber": 79 + "lineNumber": 81 }, "signature": [ "((id: string, keepAlive: string, options: ", @@ -2181,7 +2181,7 @@ ], "source": { "path": "src/plugins/data/server/search/types.ts", - "lineNumber": 69 + "lineNumber": 71 }, "initialIsOpen": false }, @@ -2200,7 +2200,7 @@ "description": [], "source": { "path": "src/plugins/data/server/search/types.ts", - "lineNumber": 34 + "lineNumber": 36 }, "signature": [ "Pick<", @@ -2211,7 +2211,7 @@ "section": "def-server.SavedObjectsClient", "text": "SavedObjectsClient" }, - ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"errors\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\">" + ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"errors\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\" | \"createPointInTimeFinder\">" ] }, { @@ -2222,7 +2222,7 @@ "description": [], "source": { "path": "src/plugins/data/server/search/types.ts", - "lineNumber": 35 + "lineNumber": 37 }, "signature": [ { @@ -2242,7 +2242,7 @@ "description": [], "source": { "path": "src/plugins/data/server/search/types.ts", - "lineNumber": 36 + "lineNumber": 38 }, "signature": [ { @@ -2262,7 +2262,7 @@ "description": [], "source": { "path": "src/plugins/data/server/search/types.ts", - "lineNumber": 37 + "lineNumber": 39 }, "signature": [ { @@ -2278,7 +2278,7 @@ ], "source": { "path": "src/plugins/data/server/search/types.ts", - "lineNumber": 33 + "lineNumber": 35 }, "initialIsOpen": false }, @@ -2344,7 +2344,23 @@ } ], "enums": [], - "misc": [], + "misc": [ + { + "id": "def-server.SearchRequestHandlerContext", + "type": "Type", + "label": "SearchRequestHandlerContext", + "tags": [], + "description": [], + "source": { + "path": "src/plugins/data/server/search/types.ts", + "lineNumber": 118 + }, + "signature": [ + "IScopedSearchClient" + ], + "initialIsOpen": false + } + ], "objects": [] }, "common": { @@ -9455,6 +9471,70 @@ "returnComment": [], "initialIsOpen": false }, + { + "id": "def-common.getKibanaContextFn", + "type": "Function", + "children": [ + { + "type": "Function", + "label": "getStartDependencies", + "isRequired": true, + "signature": [ + "(getKibanaRequest: (() => ", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreHttpPluginApi", + "section": "def-server.KibanaRequest", + "text": "KibanaRequest" + }, + ") | undefined) => Promise<", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.KibanaContextStartDependencies", + "text": "KibanaContextStartDependencies" + }, + ">" + ], + "description": [], + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_context.ts", + "lineNumber": 52 + } + } + ], + "signature": [ + "(getStartDependencies: (getKibanaRequest: (() => ", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreHttpPluginApi", + "section": "def-server.KibanaRequest", + "text": "KibanaRequest" + }, + ") | undefined) => Promise<", + "KibanaContextStartDependencies", + ">) => ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.ExpressionFunctionKibanaContext", + "text": "ExpressionFunctionKibanaContext" + } + ], + "description": [], + "label": "getKibanaContextFn", + "source": { + "path": "src/plugins/data/common/search/expressions/kibana_context.ts", + "lineNumber": 51 + }, + "tags": [], + "returnComment": [], + "initialIsOpen": false + }, { "id": "def-common.getMaxMetricAgg", "type": "Function", @@ -10015,8 +10095,7 @@ "docId": "kibDataSearchPluginApi", "section": "def-common.ISearchRequestParams", "text": "ISearchRequestParams" - }, - ">" + } ], "description": [], "children": [ @@ -15167,7 +15246,7 @@ ], "source": { "path": "src/plugins/data/common/search/search_source/fetch/types.ts", - "lineNumber": 33 + "lineNumber": 36 }, "signature": [ { @@ -15477,7 +15556,7 @@ "section": "def-common.ISearchRequestParams", "text": "ISearchRequestParams" }, - ">>" + ">" ], "description": [], "tags": [], @@ -15490,7 +15569,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/es_search/types.ts", - "lineNumber": 20 + "lineNumber": 19 }, "signature": [ "string | undefined" @@ -15499,7 +15578,7 @@ ], "source": { "path": "src/plugins/data/common/search/es_search/types.ts", - "lineNumber": 19 + "lineNumber": 18 }, "initialIsOpen": false }, @@ -16090,7 +16169,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/search_source/legacy/types.ts", - "lineNumber": 36 + "lineNumber": 35 }, "signature": [ "(params: { body: ", @@ -16120,7 +16199,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/search_source/legacy/types.ts", - "lineNumber": 40 + "lineNumber": 39 }, "signature": [ "BehaviorSubject", @@ -16130,7 +16209,7 @@ ], "source": { "path": "src/plugins/data/common/search/search_source/legacy/types.ts", - "lineNumber": 35 + "lineNumber": 34 }, "initialIsOpen": false }, @@ -16224,7 +16303,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/search_source/legacy/types.ts", - "lineNumber": 26 + "lineNumber": 25 }, "signature": [ "MsearchRequest[]" @@ -16233,7 +16312,7 @@ ], "source": { "path": "src/plugins/data/common/search/search_source/legacy/types.ts", - "lineNumber": 25 + "lineNumber": 24 }, "initialIsOpen": false }, @@ -16252,21 +16331,19 @@ "description": [], "source": { "path": "src/plugins/data/common/search/search_source/legacy/types.ts", - "lineNumber": 31 + "lineNumber": 30 }, "signature": [ "ApiResponse", "<{ responses: ", "SearchResponse", - "[]; }, ", - "Context", - ">" + "[]; }, unknown>" ] } ], "source": { "path": "src/plugins/data/common/search/search_source/legacy/types.ts", - "lineNumber": 30 + "lineNumber": 29 }, "initialIsOpen": false }, @@ -16613,7 +16690,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/search_source/fetch/types.ts", - "lineNumber": 37 + "lineNumber": 40 } }, { @@ -16624,7 +16701,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/search_source/fetch/types.ts", - "lineNumber": 38 + "lineNumber": 41 } }, { @@ -16635,7 +16712,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/search_source/fetch/types.ts", - "lineNumber": 39 + "lineNumber": 42 } }, { @@ -16646,7 +16723,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/search_source/fetch/types.ts", - "lineNumber": 40 + "lineNumber": 43 } }, { @@ -16657,7 +16734,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/search_source/fetch/types.ts", - "lineNumber": 41 + "lineNumber": 44 } }, { @@ -16668,13 +16745,13 @@ "description": [], "source": { "path": "src/plugins/data/common/search/search_source/fetch/types.ts", - "lineNumber": 42 + "lineNumber": 45 } } ], "source": { "path": "src/plugins/data/common/search/search_source/fetch/types.ts", - "lineNumber": 36 + "lineNumber": 39 }, "initialIsOpen": false }, @@ -17568,7 +17645,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/search_source/legacy/types.ts", - "lineNumber": 49 + "lineNumber": 48 } }, { @@ -17579,7 +17656,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/search_source/legacy/types.ts", - "lineNumber": 50 + "lineNumber": 49 }, "signature": [ "(params: ", @@ -17604,7 +17681,7 @@ ], "source": { "path": "src/plugins/data/common/search/search_source/legacy/types.ts", - "lineNumber": 48 + "lineNumber": 47 }, "initialIsOpen": false }, @@ -17633,7 +17710,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/search_source/legacy/types.ts", - "lineNumber": 54 + "lineNumber": 53 }, "signature": [ "Promise<", @@ -17649,7 +17726,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/search_source/legacy/types.ts", - "lineNumber": 55 + "lineNumber": 54 }, "signature": [ "() => void" @@ -17658,7 +17735,7 @@ ], "source": { "path": "src/plugins/data/common/search/search_source/legacy/types.ts", - "lineNumber": 53 + "lineNumber": 52 }, "initialIsOpen": false }, @@ -17694,7 +17771,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/search_source/legacy/types.ts", - "lineNumber": 44 + "lineNumber": 43 }, "signature": [ "Record[]" @@ -17703,7 +17780,7 @@ ], "source": { "path": "src/plugins/data/common/search/search_source/legacy/types.ts", - "lineNumber": 43 + "lineNumber": 42 }, "initialIsOpen": false }, @@ -18653,7 +18730,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/es_search/types.ts", - "lineNumber": 13 + "lineNumber": 12 }, "signature": [ "\"es\"" @@ -18877,7 +18954,7 @@ "description": [], "source": { "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 26 + "lineNumber": 34 }, "signature": [ "ExpressionFunctionDefinition<\"kibana_context\", ", @@ -19315,10 +19392,10 @@ "description": [], "source": { "path": "src/plugins/data/common/search/es_search/types.ts", - "lineNumber": 23 + "lineNumber": 22 }, "signature": [ - "IKibanaSearchResponse>" + "IKibanaSearchResponse>" ], "initialIsOpen": false }, @@ -19530,10 +19607,10 @@ "description": [], "source": { "path": "src/plugins/data/common/search/es_search/types.ts", - "lineNumber": 15 + "lineNumber": 14 }, "signature": [ - "{ trackTotalHits?: boolean | undefined; } & Search" + "{ trackTotalHits?: boolean | undefined; } & estypes.SearchRequest" ], "initialIsOpen": false }, @@ -20649,426 +20726,6 @@ }, "initialIsOpen": false }, - { - "id": "def-common.kibanaContextFunction", - "type": "Object", - "tags": [], - "children": [ - { - "tags": [], - "id": "def-common.kibanaContextFunction.name", - "type": "string", - "label": "name", - "description": [], - "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 44 - }, - "signature": [ - "\"kibana_context\"" - ] - }, - { - "tags": [], - "id": "def-common.kibanaContextFunction.type", - "type": "string", - "label": "type", - "description": [], - "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 45 - }, - "signature": [ - "\"kibana_context\"" - ] - }, - { - "tags": [], - "id": "def-common.kibanaContextFunction.inputTypes", - "type": "Array", - "label": "inputTypes", - "description": [], - "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 46 - }, - "signature": [ - "(\"kibana_context\" | \"null\")[]" - ] - }, - { - "tags": [], - "id": "def-common.kibanaContextFunction.help", - "type": "string", - "label": "help", - "description": [], - "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 47 - } - }, - { - "id": "def-common.kibanaContextFunction.args", - "type": "Object", - "tags": [], - "children": [ - { - "id": "def-common.kibanaContextFunction.args.q", - "type": "Object", - "tags": [], - "children": [ - { - "tags": [], - "id": "def-common.kibanaContextFunction.args.q.types", - "type": "Array", - "label": "types", - "description": [], - "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 52 - }, - "signature": [ - "(\"null\" | \"kibana_query\")[]" - ] - }, - { - "tags": [], - "id": "def-common.kibanaContextFunction.args.q.aliases", - "type": "Array", - "label": "aliases", - "description": [], - "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 53 - }, - "signature": [ - "string[]" - ] - }, - { - "tags": [], - "id": "def-common.kibanaContextFunction.args.q.default", - "type": "Uncategorized", - "label": "default", - "description": [], - "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 54 - }, - "signature": [ - "null" - ] - }, - { - "tags": [], - "id": "def-common.kibanaContextFunction.args.q.help", - "type": "string", - "label": "help", - "description": [], - "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 55 - } - } - ], - "description": [], - "label": "q", - "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 51 - } - }, - { - "id": "def-common.kibanaContextFunction.args.filters", - "type": "Object", - "tags": [], - "children": [ - { - "tags": [], - "id": "def-common.kibanaContextFunction.args.filters.types", - "type": "Array", - "label": "types", - "description": [], - "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 60 - }, - "signature": [ - "(\"null\" | \"kibana_filter\")[]" - ] - }, - { - "tags": [], - "id": "def-common.kibanaContextFunction.args.filters.multi", - "type": "boolean", - "label": "multi", - "description": [], - "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 61 - }, - "signature": [ - "true" - ] - }, - { - "tags": [], - "id": "def-common.kibanaContextFunction.args.filters.help", - "type": "string", - "label": "help", - "description": [], - "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 62 - } - } - ], - "description": [], - "label": "filters", - "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 59 - } - }, - { - "id": "def-common.kibanaContextFunction.args.timeRange", - "type": "Object", - "tags": [], - "children": [ - { - "tags": [], - "id": "def-common.kibanaContextFunction.args.timeRange.types", - "type": "Array", - "label": "types", - "description": [], - "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 67 - }, - "signature": [ - "(\"null\" | \"timerange\")[]" - ] - }, - { - "tags": [], - "id": "def-common.kibanaContextFunction.args.timeRange.default", - "type": "Uncategorized", - "label": "default", - "description": [], - "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 68 - }, - "signature": [ - "null" - ] - }, - { - "tags": [], - "id": "def-common.kibanaContextFunction.args.timeRange.help", - "type": "string", - "label": "help", - "description": [], - "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 69 - } - } - ], - "description": [], - "label": "timeRange", - "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 66 - } - }, - { - "id": "def-common.kibanaContextFunction.args.savedSearchId", - "type": "Object", - "tags": [], - "children": [ - { - "tags": [], - "id": "def-common.kibanaContextFunction.args.savedSearchId.types", - "type": "Array", - "label": "types", - "description": [], - "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 74 - }, - "signature": [ - "(\"string\" | \"null\")[]" - ] - }, - { - "tags": [], - "id": "def-common.kibanaContextFunction.args.savedSearchId.default", - "type": "Uncategorized", - "label": "default", - "description": [], - "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 75 - }, - "signature": [ - "null" - ] - }, - { - "tags": [], - "id": "def-common.kibanaContextFunction.args.savedSearchId.help", - "type": "string", - "label": "help", - "description": [], - "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 76 - } - } - ], - "description": [], - "label": "savedSearchId", - "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 73 - } - } - ], - "description": [], - "label": "args", - "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 50 - } - }, - { - "id": "def-common.kibanaContextFunction.fn", - "type": "Function", - "label": "fn", - "signature": [ - "(input: Input, args: Arguments, { getSavedObject }: ", - { - "pluginId": "expressions", - "scope": "common", - "docId": "kibExpressionsPluginApi", - "section": "def-common.ExecutionContext", - "text": "ExecutionContext" - }, - "<", - { - "pluginId": "inspector", - "scope": "common", - "docId": "kibInspectorPluginApi", - "section": "def-common.Adapters", - "text": "Adapters" - }, - ", ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.ExecutionContextSearch", - "text": "ExecutionContextSearch" - }, - ">) => Promise<{ type: \"kibana_context\"; query: ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataQueryPluginApi", - "section": "def-common.Query", - "text": "Query" - }, - "[]; filters: ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataPluginApi", - "section": "def-common.Filter", - "text": "Filter" - } - ], - "description": [], - "children": [ - { - "type": "CompoundType", - "label": "input", - "isRequired": false, - "signature": [ - "Input" - ], - "description": [], - "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 82 - } - }, - { - "type": "Object", - "label": "args", - "isRequired": true, - "signature": [ - "Arguments" - ], - "description": [], - "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 82 - } - }, - { - "type": "Object", - "label": "{ getSavedObject }", - "isRequired": true, - "signature": [ - { - "pluginId": "expressions", - "scope": "common", - "docId": "kibExpressionsPluginApi", - "section": "def-common.ExecutionContext", - "text": "ExecutionContext" - }, - "<", - { - "pluginId": "inspector", - "scope": "common", - "docId": "kibInspectorPluginApi", - "section": "def-common.Adapters", - "text": "Adapters" - }, - ", ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.ExecutionContextSearch", - "text": "ExecutionContextSearch" - }, - ">" - ], - "description": [], - "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 82 - } - } - ], - "tags": [], - "returnComment": [], - "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 82 - } - } - ], - "description": [], - "label": "kibanaContextFunction", - "source": { - "path": "src/plugins/data/common/search/expressions/kibana_context.ts", - "lineNumber": 43 - }, - "initialIsOpen": false - }, { "id": "def-common.kibanaFilterFunction", "type": "Object", diff --git a/api_docs/data_search.mdx b/api_docs/data_search.mdx index 70cbd231252480..370bd2ffd101e8 100644 --- a/api_docs/data_search.mdx +++ b/api_docs/data_search.mdx @@ -36,6 +36,9 @@ import dataSearchObj from './data_search.json'; ### Interfaces +### Consts, variables and types + + ## Common ### Objects diff --git a/api_docs/event_log.json b/api_docs/event_log.json index 64266b05635567..aea4d0c0019131 100644 --- a/api_docs/event_log.json +++ b/api_docs/event_log.json @@ -225,7 +225,7 @@ "description": [], "source": { "path": "x-pack/plugins/event_log/server/es/cluster_client_adapter.ts", - "lineNumber": 35 + "lineNumber": 36 } }, { @@ -236,7 +236,7 @@ "description": [], "source": { "path": "x-pack/plugins/event_log/server/es/cluster_client_adapter.ts", - "lineNumber": 36 + "lineNumber": 37 } }, { @@ -247,7 +247,7 @@ "description": [], "source": { "path": "x-pack/plugins/event_log/server/es/cluster_client_adapter.ts", - "lineNumber": 37 + "lineNumber": 38 } }, { @@ -258,7 +258,7 @@ "description": [], "source": { "path": "x-pack/plugins/event_log/server/es/cluster_client_adapter.ts", - "lineNumber": 38 + "lineNumber": 39 }, "signature": [ "(Readonly<{ '@timestamp'?: string | undefined; kibana?: Readonly<{ server_uuid?: string | undefined; alerting?: Readonly<{ status?: string | undefined; instance_id?: string | undefined; action_group_id?: string | undefined; action_subgroup?: string | undefined; } & {}> | undefined; saved_objects?: Readonly<{ type?: string | undefined; id?: string | undefined; rel?: string | undefined; namespace?: string | undefined; } & {}>[] | undefined; } & {}> | undefined; user?: Readonly<{ name?: string | undefined; } & {}> | undefined; error?: Readonly<{ message?: string | undefined; } & {}> | undefined; message?: string | undefined; tags?: string[] | undefined; ecs?: Readonly<{ version?: string | undefined; } & {}> | undefined; event?: Readonly<{ start?: string | undefined; end?: string | undefined; action?: string | undefined; provider?: string | undefined; duration?: number | undefined; outcome?: string | undefined; reason?: string | undefined; } & {}> | undefined; } & {}> | undefined)[]" @@ -267,7 +267,7 @@ ], "source": { "path": "x-pack/plugins/event_log/server/es/cluster_client_adapter.ts", - "lineNumber": 34 + "lineNumber": 35 }, "initialIsOpen": false } diff --git a/api_docs/expressions.json b/api_docs/expressions.json index eefffb009be2ac..06c97e497ae41f 100644 --- a/api_docs/expressions.json +++ b/api_docs/expressions.json @@ -1562,7 +1562,7 @@ "description": [], "source": { "path": "src/plugins/expressions/common/executor/executor.ts", - "lineNumber": 223 + "lineNumber": 230 } } ], @@ -1570,7 +1570,7 @@ "returnComment": [], "source": { "path": "src/plugins/expressions/common/executor/executor.ts", - "lineNumber": 223 + "lineNumber": 230 } }, { @@ -1606,7 +1606,7 @@ "description": [], "source": { "path": "src/plugins/expressions/common/executor/executor.ts", - "lineNumber": 233 + "lineNumber": 241 } }, { @@ -1619,7 +1619,7 @@ "description": [], "source": { "path": "src/plugins/expressions/common/executor/executor.ts", - "lineNumber": 233 + "lineNumber": 241 } } ], @@ -1627,7 +1627,7 @@ "returnComment": [], "source": { "path": "src/plugins/expressions/common/executor/executor.ts", - "lineNumber": 233 + "lineNumber": 241 } }, { @@ -1670,7 +1670,7 @@ "description": [], "source": { "path": "src/plugins/expressions/common/executor/executor.ts", - "lineNumber": 241 + "lineNumber": 249 } }, { @@ -1683,7 +1683,7 @@ "description": [], "source": { "path": "src/plugins/expressions/common/executor/executor.ts", - "lineNumber": 241 + "lineNumber": 249 } } ], @@ -1691,7 +1691,7 @@ "returnComment": [], "source": { "path": "src/plugins/expressions/common/executor/executor.ts", - "lineNumber": 241 + "lineNumber": 249 } }, { @@ -1715,7 +1715,7 @@ "returnComment": [], "source": { "path": "src/plugins/expressions/common/executor/executor.ts", - "lineNumber": 250 + "lineNumber": 258 } } ], @@ -2975,7 +2975,7 @@ "description": [], "source": { "path": "src/plugins/expressions/public/plugin.ts", - "lineNumber": 40 + "lineNumber": 35 } } ], @@ -2983,7 +2983,7 @@ "returnComment": [], "source": { "path": "src/plugins/expressions/public/plugin.ts", - "lineNumber": 40 + "lineNumber": 35 } }, { @@ -3028,7 +3028,7 @@ "description": [], "source": { "path": "src/plugins/expressions/public/plugin.ts", - "lineNumber": 56 + "lineNumber": 45 } } ], @@ -3036,7 +3036,7 @@ "returnComment": [], "source": { "path": "src/plugins/expressions/public/plugin.ts", - "lineNumber": 56 + "lineNumber": 45 } }, { @@ -3079,7 +3079,7 @@ "description": [], "source": { "path": "src/plugins/expressions/public/plugin.ts", - "lineNumber": 70 + "lineNumber": 59 } } ], @@ -3087,7 +3087,7 @@ "returnComment": [], "source": { "path": "src/plugins/expressions/public/plugin.ts", - "lineNumber": 70 + "lineNumber": 59 } }, { @@ -3103,13 +3103,13 @@ "returnComment": [], "source": { "path": "src/plugins/expressions/public/plugin.ts", - "lineNumber": 86 + "lineNumber": 75 } } ], "source": { "path": "src/plugins/expressions/public/plugin.ts", - "lineNumber": 37 + "lineNumber": 32 }, "initialIsOpen": false }, @@ -3180,7 +3180,7 @@ "description": [], "source": { "path": "src/plugins/expressions/public/plugin.ts", - "lineNumber": 40 + "lineNumber": 35 } } ], @@ -3188,7 +3188,7 @@ "returnComment": [], "source": { "path": "src/plugins/expressions/public/plugin.ts", - "lineNumber": 40 + "lineNumber": 35 } }, { @@ -3233,7 +3233,7 @@ "description": [], "source": { "path": "src/plugins/expressions/public/plugin.ts", - "lineNumber": 56 + "lineNumber": 45 } } ], @@ -3241,7 +3241,7 @@ "returnComment": [], "source": { "path": "src/plugins/expressions/public/plugin.ts", - "lineNumber": 56 + "lineNumber": 45 } }, { @@ -3284,7 +3284,7 @@ "description": [], "source": { "path": "src/plugins/expressions/public/plugin.ts", - "lineNumber": 70 + "lineNumber": 59 } } ], @@ -3292,7 +3292,7 @@ "returnComment": [], "source": { "path": "src/plugins/expressions/public/plugin.ts", - "lineNumber": 70 + "lineNumber": 59 } }, { @@ -3308,13 +3308,13 @@ "returnComment": [], "source": { "path": "src/plugins/expressions/public/plugin.ts", - "lineNumber": 86 + "lineNumber": 75 } } ], "source": { "path": "src/plugins/expressions/public/plugin.ts", - "lineNumber": 37 + "lineNumber": 32 }, "initialIsOpen": false }, @@ -5908,7 +5908,7 @@ ], "source": { "path": "src/plugins/expressions/common/execution/types.ts", - "lineNumber": 28 + "lineNumber": 27 }, "signature": [ "() => ExecutionContextSearch" @@ -5924,7 +5924,7 @@ ], "source": { "path": "src/plugins/expressions/common/execution/types.ts", - "lineNumber": 33 + "lineNumber": 32 }, "signature": [ "Record" @@ -5940,7 +5940,7 @@ ], "source": { "path": "src/plugins/expressions/common/execution/types.ts", - "lineNumber": 38 + "lineNumber": 37 }, "signature": [ "Record string | undefined" @@ -6012,7 +6012,7 @@ ], "source": { "path": "src/plugins/expressions/common/execution/types.ts", - "lineNumber": 60 + "lineNumber": 59 }, "signature": [ "(() => ", @@ -6026,46 +6026,6 @@ ") | undefined" ] }, - { - "tags": [], - "id": "def-public.ExecutionContext.getSavedObject", - "type": "Function", - "label": "getSavedObject", - "description": [ - "\nAllows to fetch saved objects from ElasticSearch. In browser `getSavedObject`\nfunction is provided automatically by the Expressions plugin. On the server\nthe caller of the expression has to provide this context function. The\nreason is because on the browser we always know the user who tries to\nfetch a saved object, thus saved object client is scoped automatically to\nthat user. However, on the server we can scope that saved object client to\nany user, or even not scope it at all and execute it as an \"internal\" user." - ], - "source": { - "path": "src/plugins/expressions/common/execution/types.ts", - "lineNumber": 71 - }, - "signature": [ - "((type: string, id: string) => Promise<", - { - "pluginId": "core", - "scope": "common", - "docId": "kibCorePluginApi", - "section": "def-common.SavedObject", - "text": "SavedObject" - }, - ">) | undefined" - ] - }, { "tags": [], "id": "def-public.ExecutionContext.isSyncColorsEnabled", @@ -6076,7 +6036,7 @@ ], "source": { "path": "src/plugins/expressions/common/execution/types.ts", - "lineNumber": 79 + "lineNumber": 64 }, "signature": [ "(() => boolean) | undefined" @@ -6085,7 +6045,7 @@ ], "source": { "path": "src/plugins/expressions/common/execution/types.ts", - "lineNumber": 21 + "lineNumber": 20 }, "initialIsOpen": false }, @@ -9481,7 +9441,7 @@ ], "source": { "path": "src/plugins/expressions/public/plugin.ts", - "lineNumber": 24 + "lineNumber": 19 }, "signature": [ "{ readonly getType: (name: string) => ", @@ -9562,7 +9522,7 @@ "description": [], "source": { "path": "src/plugins/expressions/public/plugin.ts", - "lineNumber": 30 + "lineNumber": 25 }, "signature": [ "typeof ", @@ -9583,7 +9543,7 @@ "description": [], "source": { "path": "src/plugins/expressions/public/plugin.ts", - "lineNumber": 31 + "lineNumber": 26 }, "signature": [ "typeof ", @@ -9604,7 +9564,7 @@ "description": [], "source": { "path": "src/plugins/expressions/public/plugin.ts", - "lineNumber": 32 + "lineNumber": 27 }, "signature": [ { @@ -9624,7 +9584,7 @@ "description": [], "source": { "path": "src/plugins/expressions/public/plugin.ts", - "lineNumber": 33 + "lineNumber": 28 }, "signature": [ "({ className, dataAttrs, padding, renderError, expression, onEvent, onData$, reload$, debounce, ...expressionLoaderOptions }: ", @@ -9646,7 +9606,7 @@ "description": [], "source": { "path": "src/plugins/expressions/public/plugin.ts", - "lineNumber": 34 + "lineNumber": 29 }, "signature": [ "typeof ", @@ -9662,7 +9622,7 @@ ], "source": { "path": "src/plugins/expressions/public/plugin.ts", - "lineNumber": 29 + "lineNumber": 24 }, "lifecycle": "start", "initialIsOpen": true @@ -11035,7 +10995,7 @@ "description": [], "source": { "path": "src/plugins/expressions/common/executor/executor.ts", - "lineNumber": 223 + "lineNumber": 230 } } ], @@ -11043,7 +11003,7 @@ "returnComment": [], "source": { "path": "src/plugins/expressions/common/executor/executor.ts", - "lineNumber": 223 + "lineNumber": 230 } }, { @@ -11079,7 +11039,7 @@ "description": [], "source": { "path": "src/plugins/expressions/common/executor/executor.ts", - "lineNumber": 233 + "lineNumber": 241 } }, { @@ -11092,7 +11052,7 @@ "description": [], "source": { "path": "src/plugins/expressions/common/executor/executor.ts", - "lineNumber": 233 + "lineNumber": 241 } } ], @@ -11100,7 +11060,7 @@ "returnComment": [], "source": { "path": "src/plugins/expressions/common/executor/executor.ts", - "lineNumber": 233 + "lineNumber": 241 } }, { @@ -11143,7 +11103,7 @@ "description": [], "source": { "path": "src/plugins/expressions/common/executor/executor.ts", - "lineNumber": 241 + "lineNumber": 249 } }, { @@ -11156,7 +11116,7 @@ "description": [], "source": { "path": "src/plugins/expressions/common/executor/executor.ts", - "lineNumber": 241 + "lineNumber": 249 } } ], @@ -11164,7 +11124,7 @@ "returnComment": [], "source": { "path": "src/plugins/expressions/common/executor/executor.ts", - "lineNumber": 241 + "lineNumber": 249 } }, { @@ -11188,7 +11148,7 @@ "returnComment": [], "source": { "path": "src/plugins/expressions/common/executor/executor.ts", - "lineNumber": 250 + "lineNumber": 258 } } ], @@ -13984,7 +13944,7 @@ ], "source": { "path": "src/plugins/expressions/common/execution/types.ts", - "lineNumber": 28 + "lineNumber": 27 }, "signature": [ "() => ExecutionContextSearch" @@ -14000,7 +13960,7 @@ ], "source": { "path": "src/plugins/expressions/common/execution/types.ts", - "lineNumber": 33 + "lineNumber": 32 }, "signature": [ "Record" @@ -14016,7 +13976,7 @@ ], "source": { "path": "src/plugins/expressions/common/execution/types.ts", - "lineNumber": 38 + "lineNumber": 37 }, "signature": [ "Record string | undefined" @@ -14088,7 +14048,7 @@ ], "source": { "path": "src/plugins/expressions/common/execution/types.ts", - "lineNumber": 60 + "lineNumber": 59 }, "signature": [ "(() => ", @@ -14102,46 +14062,6 @@ ") | undefined" ] }, - { - "tags": [], - "id": "def-server.ExecutionContext.getSavedObject", - "type": "Function", - "label": "getSavedObject", - "description": [ - "\nAllows to fetch saved objects from ElasticSearch. In browser `getSavedObject`\nfunction is provided automatically by the Expressions plugin. On the server\nthe caller of the expression has to provide this context function. The\nreason is because on the browser we always know the user who tries to\nfetch a saved object, thus saved object client is scoped automatically to\nthat user. However, on the server we can scope that saved object client to\nany user, or even not scope it at all and execute it as an \"internal\" user." - ], - "source": { - "path": "src/plugins/expressions/common/execution/types.ts", - "lineNumber": 71 - }, - "signature": [ - "((type: string, id: string) => Promise<", - { - "pluginId": "core", - "scope": "common", - "docId": "kibCorePluginApi", - "section": "def-common.SavedObject", - "text": "SavedObject" - }, - ">) | undefined" - ] - }, { "tags": [], "id": "def-server.ExecutionContext.isSyncColorsEnabled", @@ -14152,7 +14072,7 @@ ], "source": { "path": "src/plugins/expressions/common/execution/types.ts", - "lineNumber": 79 + "lineNumber": 64 }, "signature": [ "(() => boolean) | undefined" @@ -14161,7 +14081,7 @@ ], "source": { "path": "src/plugins/expressions/common/execution/types.ts", - "lineNumber": 21 + "lineNumber": 20 }, "initialIsOpen": false }, @@ -18395,7 +18315,7 @@ "description": [], "source": { "path": "src/plugins/expressions/common/executor/executor.ts", - "lineNumber": 223 + "lineNumber": 230 } } ], @@ -18403,7 +18323,7 @@ "returnComment": [], "source": { "path": "src/plugins/expressions/common/executor/executor.ts", - "lineNumber": 223 + "lineNumber": 230 } }, { @@ -18439,7 +18359,7 @@ "description": [], "source": { "path": "src/plugins/expressions/common/executor/executor.ts", - "lineNumber": 233 + "lineNumber": 241 } }, { @@ -18452,7 +18372,7 @@ "description": [], "source": { "path": "src/plugins/expressions/common/executor/executor.ts", - "lineNumber": 233 + "lineNumber": 241 } } ], @@ -18460,7 +18380,7 @@ "returnComment": [], "source": { "path": "src/plugins/expressions/common/executor/executor.ts", - "lineNumber": 233 + "lineNumber": 241 } }, { @@ -18503,7 +18423,7 @@ "description": [], "source": { "path": "src/plugins/expressions/common/executor/executor.ts", - "lineNumber": 241 + "lineNumber": 249 } }, { @@ -18516,7 +18436,7 @@ "description": [], "source": { "path": "src/plugins/expressions/common/executor/executor.ts", - "lineNumber": 241 + "lineNumber": 249 } } ], @@ -18524,7 +18444,7 @@ "returnComment": [], "source": { "path": "src/plugins/expressions/common/executor/executor.ts", - "lineNumber": 241 + "lineNumber": 249 } }, { @@ -18548,7 +18468,7 @@ "returnComment": [], "source": { "path": "src/plugins/expressions/common/executor/executor.ts", - "lineNumber": 250 + "lineNumber": 258 } } ], @@ -23230,7 +23150,7 @@ "description": [], "source": { "path": "src/plugins/expressions/common/execution/types.ts", - "lineNumber": 86 + "lineNumber": 71 }, "signature": [ { @@ -23250,7 +23170,7 @@ "description": [], "source": { "path": "src/plugins/expressions/common/execution/types.ts", - "lineNumber": 87 + "lineNumber": 72 }, "signature": [ { @@ -23265,7 +23185,7 @@ ], "source": { "path": "src/plugins/expressions/common/execution/types.ts", - "lineNumber": 85 + "lineNumber": 70 }, "initialIsOpen": false }, @@ -23362,7 +23282,7 @@ ], "source": { "path": "src/plugins/expressions/common/execution/types.ts", - "lineNumber": 28 + "lineNumber": 27 }, "signature": [ "() => ExecutionContextSearch" @@ -23378,7 +23298,7 @@ ], "source": { "path": "src/plugins/expressions/common/execution/types.ts", - "lineNumber": 33 + "lineNumber": 32 }, "signature": [ "Record" @@ -23394,7 +23314,7 @@ ], "source": { "path": "src/plugins/expressions/common/execution/types.ts", - "lineNumber": 38 + "lineNumber": 37 }, "signature": [ "Record string | undefined" @@ -23466,7 +23386,7 @@ ], "source": { "path": "src/plugins/expressions/common/execution/types.ts", - "lineNumber": 60 + "lineNumber": 59 }, "signature": [ "(() => ", @@ -23480,46 +23400,6 @@ ") | undefined" ] }, - { - "tags": [], - "id": "def-common.ExecutionContext.getSavedObject", - "type": "Function", - "label": "getSavedObject", - "description": [ - "\nAllows to fetch saved objects from ElasticSearch. In browser `getSavedObject`\nfunction is provided automatically by the Expressions plugin. On the server\nthe caller of the expression has to provide this context function. The\nreason is because on the browser we always know the user who tries to\nfetch a saved object, thus saved object client is scoped automatically to\nthat user. However, on the server we can scope that saved object client to\nany user, or even not scope it at all and execute it as an \"internal\" user." - ], - "source": { - "path": "src/plugins/expressions/common/execution/types.ts", - "lineNumber": 71 - }, - "signature": [ - "((type: string, id: string) => Promise<", - { - "pluginId": "core", - "scope": "common", - "docId": "kibCorePluginApi", - "section": "def-common.SavedObject", - "text": "SavedObject" - }, - ">) | undefined" - ] - }, { "tags": [], "id": "def-common.ExecutionContext.isSyncColorsEnabled", @@ -23530,7 +23410,7 @@ ], "source": { "path": "src/plugins/expressions/common/execution/types.ts", - "lineNumber": 79 + "lineNumber": 64 }, "signature": [ "(() => boolean) | undefined" @@ -23539,7 +23419,7 @@ ], "source": { "path": "src/plugins/expressions/common/execution/types.ts", - "lineNumber": 21 + "lineNumber": 20 }, "initialIsOpen": false }, diff --git a/api_docs/fleet.json b/api_docs/fleet.json index ed51f88ee9d5d9..60d0dca4d8a108 100644 --- a/api_docs/fleet.json +++ b/api_docs/fleet.json @@ -1789,7 +1789,7 @@ "section": "def-server.SavedObjectsClient", "text": "SavedObjectsClient" }, - ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"errors\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\">, id: string, withPackagePolicies?: boolean) => Promise<", + ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"errors\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\" | \"createPointInTimeFinder\">, id: string, withPackagePolicies?: boolean) => Promise<", { "pluginId": "fleet", "scope": "common", @@ -1819,7 +1819,7 @@ "section": "def-server.SavedObjectsClient", "text": "SavedObjectsClient" }, - ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"errors\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\">, options: Readonly<{ page?: number | undefined; perPage?: number | undefined; sortField?: string | undefined; sortOrder?: \"asc\" | \"desc\" | undefined; kuery?: any; showUpgradeable?: boolean | undefined; } & {}> & { withPackagePolicies?: boolean | undefined; }) => Promise<{ items: ", + ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"errors\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\" | \"createPointInTimeFinder\">, options: Readonly<{ page?: number | undefined; perPage?: number | undefined; sortField?: string | undefined; sortOrder?: \"asc\" | \"desc\" | undefined; kuery?: any; showUpgradeable?: boolean | undefined; } & {}> & { withPackagePolicies?: boolean | undefined; }) => Promise<{ items: ", { "pluginId": "fleet", "scope": "common", @@ -1849,7 +1849,7 @@ "section": "def-server.SavedObjectsClient", "text": "SavedObjectsClient" }, - ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"errors\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\">) => Promise" + ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"errors\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\" | \"createPointInTimeFinder\">) => Promise" ] }, { @@ -1871,7 +1871,7 @@ "section": "def-server.SavedObjectsClient", "text": "SavedObjectsClient" }, - ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"errors\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\">, id: string, options?: { standalone: boolean; } | undefined) => Promise<", + ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"errors\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\" | \"createPointInTimeFinder\">, id: string, options?: { standalone: boolean; } | undefined) => Promise<", { "pluginId": "fleet", "scope": "common", @@ -2434,7 +2434,7 @@ "section": "def-server.SavedObjectsClient", "text": "SavedObjectsClient" }, - ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"errors\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\">, pkgName: string, datasetPath: string) => Promise" + ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"errors\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\" | \"createPointInTimeFinder\">, pkgName: string, datasetPath: string) => Promise" ], "description": [], "children": [ @@ -2451,7 +2451,7 @@ "section": "def-server.SavedObjectsClient", "text": "SavedObjectsClient" }, - ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"errors\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\">" + ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"errors\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\" | \"createPointInTimeFinder\">" ], "description": [], "source": { @@ -2661,7 +2661,7 @@ "section": "def-server.SavedObjectsClient", "text": "SavedObjectsClient" }, - ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"errors\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\">, pkgName: string) => Promise<", + ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"errors\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\" | \"createPointInTimeFinder\">, pkgName: string) => Promise<", { "pluginId": "fleet", "scope": "common", @@ -2686,7 +2686,7 @@ "section": "def-server.SavedObjectsClient", "text": "SavedObjectsClient" }, - ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"errors\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\">" + ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"errors\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\" | \"createPointInTimeFinder\">" ], "description": [], "source": { @@ -3744,7 +3744,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 152 + "lineNumber": 151 } }, { @@ -3755,7 +3755,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 153 + "lineNumber": 152 }, "signature": [ { @@ -3776,7 +3776,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 154 + "lineNumber": 153 }, "signature": [ "string | undefined" @@ -3790,7 +3790,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 155 + "lineNumber": 154 }, "signature": [ "string | undefined" @@ -3804,7 +3804,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 156 + "lineNumber": 155 }, "signature": [ "string[]" @@ -3813,7 +3813,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 151 + "lineNumber": 150 }, "initialIsOpen": false }, @@ -3849,7 +3849,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 49 + "lineNumber": 48 }, "signature": [ { @@ -3869,7 +3869,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 50 + "lineNumber": 49 }, "signature": [ "any" @@ -3883,7 +3883,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 51 + "lineNumber": 50 }, "signature": [ "string | undefined" @@ -3897,7 +3897,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 52 + "lineNumber": 51 } }, { @@ -3908,7 +3908,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 53 + "lineNumber": 52 } }, { @@ -3919,7 +3919,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 54 + "lineNumber": 53 } }, { @@ -3930,7 +3930,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 55 + "lineNumber": 54 }, "signature": [ "any" @@ -3939,7 +3939,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 48 + "lineNumber": 47 }, "initialIsOpen": false }, @@ -3975,13 +3975,13 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 124 + "lineNumber": 123 } } ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 123 + "lineNumber": 122 }, "initialIsOpen": false }, @@ -4000,7 +4000,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 130 + "lineNumber": 129 }, "signature": [ "any" @@ -4009,7 +4009,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 129 + "lineNumber": 128 }, "initialIsOpen": false }, @@ -4174,7 +4174,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 59 + "lineNumber": 58 } }, { @@ -4185,7 +4185,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 60 + "lineNumber": 59 }, "signature": [ { @@ -4205,7 +4205,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 61 + "lineNumber": 60 }, "signature": [ "{ policy: ", @@ -4227,7 +4227,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 64 + "lineNumber": 63 } }, { @@ -4238,7 +4238,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 65 + "lineNumber": 64 } }, { @@ -4249,7 +4249,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 66 + "lineNumber": 65 } }, { @@ -4260,7 +4260,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 67 + "lineNumber": 66 }, "signature": [ "any" @@ -4269,7 +4269,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 58 + "lineNumber": 57 }, "initialIsOpen": false }, @@ -4298,7 +4298,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 160 + "lineNumber": 159 }, "signature": [ "string | undefined" @@ -4312,7 +4312,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 161 + "lineNumber": 160 }, "signature": [ "string[] | undefined" @@ -4321,7 +4321,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 159 + "lineNumber": 158 }, "initialIsOpen": false }, @@ -5111,7 +5111,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/rest_spec/agent.ts", - "lineNumber": 193 + "lineNumber": 200 }, "signature": [ "{ agentId: string; }" @@ -5120,7 +5120,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/rest_spec/agent.ts", - "lineNumber": 192 + "lineNumber": 199 }, "initialIsOpen": false }, @@ -5521,7 +5521,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 173 + "lineNumber": 172 }, "signature": [ "number | undefined" @@ -5537,7 +5537,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 177 + "lineNumber": 176 }, "signature": [ "string | undefined" @@ -5553,7 +5553,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 181 + "lineNumber": 180 }, "signature": [ { @@ -5575,7 +5575,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 185 + "lineNumber": 184 } }, { @@ -5588,7 +5588,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 189 + "lineNumber": 188 } }, { @@ -5601,7 +5601,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 193 + "lineNumber": 192 }, "signature": [ "string | undefined" @@ -5617,7 +5617,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 197 + "lineNumber": 196 }, "signature": [ "string | undefined" @@ -5633,7 +5633,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 201 + "lineNumber": 200 }, "signature": [ "string | null | undefined" @@ -5649,7 +5649,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 205 + "lineNumber": 204 }, "signature": [ "string | null | undefined" @@ -5665,7 +5665,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 209 + "lineNumber": 208 }, "signature": [ "string | undefined" @@ -5679,7 +5679,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 210 + "lineNumber": 209 }, "signature": [ { @@ -5702,7 +5702,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 214 + "lineNumber": 213 }, "signature": [ { @@ -5724,7 +5724,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 218 + "lineNumber": 217 }, "signature": [ { @@ -5746,7 +5746,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 222 + "lineNumber": 221 }, "signature": [ "string | undefined" @@ -5762,7 +5762,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 226 + "lineNumber": 225 }, "signature": [ "number | null | undefined" @@ -5778,7 +5778,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 230 + "lineNumber": 229 }, "signature": [ "number | undefined" @@ -5794,7 +5794,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 234 + "lineNumber": 233 }, "signature": [ "string | undefined" @@ -5810,7 +5810,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 238 + "lineNumber": 237 }, "signature": [ "string | undefined" @@ -5826,7 +5826,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 242 + "lineNumber": 241 }, "signature": [ "\"online\" | \"error\" | \"updating\" | \"degraded\" | undefined" @@ -5842,7 +5842,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 246 + "lineNumber": 245 }, "signature": [ "string | undefined" @@ -5858,7 +5858,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 250 + "lineNumber": 249 }, "signature": [ "string | undefined" @@ -5874,7 +5874,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 254 + "lineNumber": 253 }, "signature": [ "string | undefined" @@ -5890,7 +5890,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 258 + "lineNumber": 257 }, "signature": [ "string[] | undefined" @@ -5906,7 +5906,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 262 + "lineNumber": 261 }, "signature": [ "number | undefined" @@ -5915,7 +5915,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 169 + "lineNumber": 168 }, "initialIsOpen": false }, @@ -5938,7 +5938,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 286 + "lineNumber": 285 }, "signature": [ "string | undefined" @@ -5954,7 +5954,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 290 + "lineNumber": 289 }, "signature": [ "number | undefined" @@ -5970,7 +5970,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 294 + "lineNumber": 293 }, "signature": [ "string | undefined" @@ -5986,7 +5986,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 298 + "lineNumber": 297 }, "signature": [ "string | undefined" @@ -6002,7 +6002,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 302 + "lineNumber": 301 }, "signature": [ "string | undefined" @@ -6018,7 +6018,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 306 + "lineNumber": 305 }, "signature": [ "string | undefined" @@ -6034,7 +6034,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 310 + "lineNumber": 309 }, "signature": [ "string | undefined" @@ -6050,7 +6050,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 314 + "lineNumber": 313 }, "signature": [ "string[] | undefined" @@ -6066,7 +6066,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 318 + "lineNumber": 317 }, "signature": [ "{ [k: string]: unknown; } | undefined" @@ -6080,7 +6080,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 321 + "lineNumber": 320 }, "signature": [ "any" @@ -6089,7 +6089,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 282 + "lineNumber": 281 }, "initialIsOpen": false }, @@ -6112,7 +6112,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 271 + "lineNumber": 270 } }, { @@ -6125,7 +6125,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 275 + "lineNumber": 274 } }, { @@ -6136,7 +6136,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 276 + "lineNumber": 275 }, "signature": [ "any" @@ -6145,7 +6145,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 267 + "lineNumber": 266 }, "initialIsOpen": false }, @@ -6968,7 +6968,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/rest_spec/agent.ts", - "lineNumber": 208 + "lineNumber": 215 }, "signature": [ "{ kuery?: string | undefined; policyId?: string | undefined; }" @@ -6977,7 +6977,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/rest_spec/agent.ts", - "lineNumber": 207 + "lineNumber": 214 }, "initialIsOpen": false }, @@ -6996,7 +6996,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/rest_spec/agent.ts", - "lineNumber": 215 + "lineNumber": 222 }, "signature": [ "{ events: number; total: number; online: number; error: number; offline: number; other: number; updating: number; }" @@ -7005,7 +7005,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/rest_spec/agent.ts", - "lineNumber": 214 + "lineNumber": 221 }, "initialIsOpen": false }, @@ -7436,7 +7436,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/rest_spec/agent.ts", - "lineNumber": 175 + "lineNumber": 182 }, "signature": [ "{ agentId: string; }" @@ -7450,7 +7450,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/rest_spec/agent.ts", - "lineNumber": 178 + "lineNumber": 185 }, "signature": [ "{ page: number; perPage: number; kuery?: string | undefined; }" @@ -7459,7 +7459,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/rest_spec/agent.ts", - "lineNumber": 174 + "lineNumber": 181 }, "initialIsOpen": false }, @@ -7478,7 +7478,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/rest_spec/agent.ts", - "lineNumber": 186 + "lineNumber": 193 }, "signature": [ { @@ -7499,7 +7499,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/rest_spec/agent.ts", - "lineNumber": 187 + "lineNumber": 194 } }, { @@ -7510,7 +7510,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/rest_spec/agent.ts", - "lineNumber": 188 + "lineNumber": 195 } }, { @@ -7521,13 +7521,13 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/rest_spec/agent.ts", - "lineNumber": 189 + "lineNumber": 196 } } ], "source": { "path": "x-pack/plugins/fleet/common/types/rest_spec/agent.ts", - "lineNumber": 185 + "lineNumber": 192 }, "initialIsOpen": false }, @@ -8883,7 +8883,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 43 + "lineNumber": 42 }, "signature": [ { @@ -8903,7 +8903,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 44 + "lineNumber": 43 }, "signature": [ "any" @@ -8917,7 +8917,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 45 + "lineNumber": 44 }, "signature": [ "string | undefined" @@ -8926,7 +8926,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 42 + "lineNumber": 41 }, "initialIsOpen": false }, @@ -8945,7 +8945,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 98 + "lineNumber": 97 }, "signature": [ "\"STATE\" | \"ERROR\" | \"ACTION_RESULT\" | \"ACTION\"" @@ -8959,7 +8959,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 99 + "lineNumber": 98 }, "signature": [ "\"RUNNING\" | \"STARTING\" | \"IN_PROGRESS\" | \"CONFIG\" | \"FAILED\" | \"STOPPING\" | \"STOPPED\" | \"DEGRADED\" | \"UPDATING\" | \"DATA_DUMP\" | \"ACKNOWLEDGED\" | \"UNKNOWN\"" @@ -8973,7 +8973,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 114 + "lineNumber": 113 } }, { @@ -8984,7 +8984,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 115 + "lineNumber": 114 } }, { @@ -8995,7 +8995,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 116 + "lineNumber": 115 }, "signature": [ "any" @@ -9009,7 +9009,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 117 + "lineNumber": 116 } }, { @@ -9020,7 +9020,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 118 + "lineNumber": 117 }, "signature": [ "string | undefined" @@ -9034,7 +9034,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 119 + "lineNumber": 118 }, "signature": [ "string | undefined" @@ -9048,7 +9048,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 120 + "lineNumber": 119 }, "signature": [ "string | undefined" @@ -9057,7 +9057,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 97 + "lineNumber": 96 }, "initialIsOpen": false }, @@ -10708,7 +10708,7 @@ "children": [], "source": { "path": "x-pack/plugins/fleet/common/types/rest_spec/agent.ts", - "lineNumber": 148 + "lineNumber": 154 }, "initialIsOpen": false }, @@ -10727,7 +10727,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/rest_spec/agent.ts", - "lineNumber": 161 + "lineNumber": 167 }, "signature": [ "{ policy_id: string; agents: string | string[]; }" @@ -10736,35 +10736,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/rest_spec/agent.ts", - "lineNumber": 160 - }, - "initialIsOpen": false - }, - { - "id": "def-common.PostBulkAgentReassignResponse", - "type": "Interface", - "label": "PostBulkAgentReassignResponse", - "description": [], - "tags": [], - "children": [ - { - "id": "def-common.PostBulkAgentReassignResponse.Unnamed", - "type": "Any", - "label": "Unnamed", - "tags": [], - "description": [], - "source": { - "path": "x-pack/plugins/fleet/common/types/rest_spec/agent.ts", - "lineNumber": 168 - }, - "signature": [ - "any" - ] - } - ], - "source": { - "path": "x-pack/plugins/fleet/common/types/rest_spec/agent.ts", - "lineNumber": 167 + "lineNumber": 166 }, "initialIsOpen": false }, @@ -10837,19 +10809,6 @@ }, "initialIsOpen": false }, - { - "id": "def-common.PostBulkAgentUpgradeResponse", - "type": "Interface", - "label": "PostBulkAgentUpgradeResponse", - "description": [], - "tags": [], - "children": [], - "source": { - "path": "x-pack/plugins/fleet/common/types/rest_spec/agent.ts", - "lineNumber": 145 - }, - "initialIsOpen": false - }, { "id": "def-common.PostEnrollmentAPIKeyRequest", "type": "Interface", @@ -11047,7 +11006,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/rest_spec/agent.ts", - "lineNumber": 151 + "lineNumber": 157 }, "signature": [ "{ agentId: string; }" @@ -11061,7 +11020,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/rest_spec/agent.ts", - "lineNumber": 154 + "lineNumber": 160 }, "signature": [ "{ policy_id: string; }" @@ -11070,7 +11029,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/rest_spec/agent.ts", - "lineNumber": 150 + "lineNumber": 156 }, "initialIsOpen": false }, @@ -11083,7 +11042,7 @@ "children": [], "source": { "path": "x-pack/plugins/fleet/common/types/rest_spec/agent.ts", - "lineNumber": 158 + "lineNumber": 164 }, "initialIsOpen": false }, @@ -12138,7 +12097,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/rest_spec/agent.ts", - "lineNumber": 199 + "lineNumber": 206 }, "signature": [ "{ agentId: string; }" @@ -12152,7 +12111,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/rest_spec/agent.ts", - "lineNumber": 202 + "lineNumber": 209 }, "signature": [ "{ user_provided_metadata: Record; }" @@ -12161,7 +12120,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/rest_spec/agent.ts", - "lineNumber": 198 + "lineNumber": 205 }, "initialIsOpen": false }, @@ -12597,7 +12556,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 88 + "lineNumber": 87 }, "signature": [ "CommonAgentActionSOAttributes & { agent_id: string; }" @@ -12615,7 +12574,7 @@ "lineNumber": 34 }, "signature": [ - "\"POLICY_CHANGE\" | \"UNENROLL\" | \"UPGRADE\" | \"SETTINGS\" | \"INTERNAL_POLICY_REASSIGN\"" + "\"POLICY_CHANGE\" | \"UNENROLL\" | \"UPGRADE\" | \"SETTINGS\" | \"POLICY_REASSIGN\"" ], "initialIsOpen": false }, @@ -12642,7 +12601,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 127 + "lineNumber": 126 }, "signature": [ "NewAgentEvent" @@ -12657,7 +12616,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 91 + "lineNumber": 90 }, "signature": [ "CommonAgentActionSOAttributes & { policy_id: string; policy_revision: number; }" @@ -12672,7 +12631,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 72 + "lineNumber": 71 }, "signature": [ "Pick & { type: 'CONFIG_CHANGE'; data: { config: FullAgentPolicy;}; }" @@ -12920,7 +12879,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/agent.ts", - "lineNumber": 95 + "lineNumber": 94 }, "signature": [ { @@ -13822,6 +13781,36 @@ ], "initialIsOpen": false }, + { + "id": "def-common.PostBulkAgentReassignResponse", + "type": "Type", + "label": "PostBulkAgentReassignResponse", + "tags": [], + "description": [], + "source": { + "path": "x-pack/plugins/fleet/common/types/rest_spec/agent.ts", + "lineNumber": 173 + }, + "signature": [ + "{ [x: string]: { success: boolean; error?: string | undefined; }; }" + ], + "initialIsOpen": false + }, + { + "id": "def-common.PostBulkAgentUpgradeResponse", + "type": "Type", + "label": "PostBulkAgentUpgradeResponse", + "tags": [], + "description": [], + "source": { + "path": "x-pack/plugins/fleet/common/types/rest_spec/agent.ts", + "lineNumber": 145 + }, + "signature": [ + "{ [x: string]: { success: boolean; error?: string | undefined; }; }" + ], + "initialIsOpen": false + }, { "id": "def-common.RegistryPackage", "type": "Type", diff --git a/api_docs/global_search.json b/api_docs/global_search.json index 985abf3417935a..0cb40be77a7aeb 100644 --- a/api_docs/global_search.json +++ b/api_docs/global_search.json @@ -704,7 +704,7 @@ "section": "def-server.SavedObjectsClient", "text": "SavedObjectsClient" }, - ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"errors\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\">; typeRegistry: Pick<", + ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"errors\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\" | \"createPointInTimeFinder\">; typeRegistry: Pick<", { "pluginId": "core", "scope": "server", diff --git a/api_docs/index_management.json b/api_docs/index_management.json index 8c6ba711642ffd..6c66a94bf59f5d 100644 --- a/api_docs/index_management.json +++ b/api_docs/index_management.json @@ -1121,13 +1121,7 @@ "lineNumber": 58 }, "signature": [ - { - "pluginId": "indexManagement", - "scope": "common", - "docId": "kibIndexManagementPluginApi", - "section": "def-common.Health", - "text": "Health" - } + "ClusterStatus" ] }, { diff --git a/api_docs/infra.json b/api_docs/infra.json index a10be8ce8bf04b..9d64df881fc528 100644 --- a/api_docs/infra.json +++ b/api_docs/infra.json @@ -198,7 +198,7 @@ "description": [], "source": { "path": "x-pack/plugins/infra/server/plugin.ts", - "lineNumber": 64 + "lineNumber": 65 }, "signature": [ "{ readonly sources?: Readonly<{ default?: Readonly<{ fields?: Readonly<{ host?: string | undefined; message?: string[] | undefined; timestamp?: string | undefined; tiebreaker?: string | undefined; container?: string | undefined; pod?: string | undefined; } & {}> | undefined; logAlias?: string | undefined; metricAlias?: string | undefined; } & {}> | undefined; } & {}> | undefined; readonly enabled: boolean; readonly query: Readonly<{} & { partitionSize: number; partitionFactor: number; }>; }" @@ -222,7 +222,7 @@ "description": [], "source": { "path": "x-pack/plugins/infra/server/plugin.ts", - "lineNumber": 75 + "lineNumber": 76 }, "signature": [ "(sourceId: string, sourceProperties: ", @@ -239,7 +239,7 @@ ], "source": { "path": "x-pack/plugins/infra/server/plugin.ts", - "lineNumber": 74 + "lineNumber": 75 }, "lifecycle": "setup", "initialIsOpen": true diff --git a/api_docs/lens.json b/api_docs/lens.json index e586016c22fc3f..b5321bf24ba6b0 100644 --- a/api_docs/lens.json +++ b/api_docs/lens.json @@ -82,7 +82,7 @@ "lineNumber": 43 }, "signature": [ - "\"cardinality\"" + "\"unique_count\"" ] } ], @@ -238,7 +238,7 @@ "description": [], "source": { "path": "x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filters.tsx", - "lineNumber": 73 + "lineNumber": 74 }, "signature": [ "\"filters\"" @@ -252,7 +252,7 @@ "description": [], "source": { "path": "x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filters.tsx", - "lineNumber": 74 + "lineNumber": 75 }, "signature": [ "{ filters: ", @@ -269,7 +269,7 @@ ], "source": { "path": "x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filters.tsx", - "lineNumber": 72 + "lineNumber": 73 }, "initialIsOpen": false }, @@ -291,7 +291,7 @@ "lineNumber": 46 }, "signature": [ - "\"range\" | \"filters\" | \"count\" | \"max\" | \"min\" | \"date_histogram\" | \"sum\" | \"terms\" | \"avg\" | \"median\" | \"cumulative_sum\" | \"derivative\" | \"moving_average\" | \"counter_rate\" | \"cardinality\" | \"percentile\" | \"last_value\" | undefined" + "\"range\" | \"filters\" | \"count\" | \"max\" | \"min\" | \"date_histogram\" | \"sum\" | \"average\" | \"terms\" | \"median\" | \"cumulative_sum\" | \"moving_average\" | \"counter_rate\" | \"differences\" | \"unique_count\" | \"percentile\" | \"last_value\" | undefined" ] }, { @@ -538,6 +538,30 @@ "signature": [ "() => boolean" ] + }, + { + "tags": [], + "id": "def-public.LensPublicStart.getXyVisTypes", + "type": "Function", + "label": "getXyVisTypes", + "description": [ + "\nMethod which returns xy VisualizationTypes array keeping this async as to not impact page load bundle" + ], + "source": { + "path": "x-pack/plugins/lens/public/plugin.ts", + "lineNumber": 108 + }, + "signature": [ + "() => Promise<", + { + "pluginId": "lens", + "scope": "public", + "docId": "kibLensPluginApi", + "section": "def-public.VisualizationType", + "text": "VisualizationType" + }, + "[]>" + ] } ], "source": { @@ -929,7 +953,7 @@ "description": [], "source": { "path": "x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/index.tsx", - "lineNumber": 59 + "lineNumber": 57 }, "signature": [ "\"terms\"" @@ -943,7 +967,7 @@ "description": [], "source": { "path": "x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/index.tsx", - "lineNumber": 60 + "lineNumber": 58 }, "signature": [ "{ size: number; orderBy: { type: \"alphabetical\"; } | { type: \"column\"; columnId: string; }; orderDirection: \"asc\" | \"desc\"; otherBucket?: boolean | undefined; missingBucket?: boolean | undefined; format?: { id: string; params?: { decimals: number; } | undefined; } | undefined; }" @@ -952,7 +976,7 @@ ], "source": { "path": "x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/index.tsx", - "lineNumber": 58 + "lineNumber": 56 }, "initialIsOpen": false }, @@ -1114,7 +1138,7 @@ "description": [], "source": { "path": "x-pack/plugins/lens/public/xy_visualization/types.ts", - "lineNumber": 420 + "lineNumber": 423 }, "signature": [ { @@ -1134,7 +1158,7 @@ "description": [], "source": { "path": "x-pack/plugins/lens/public/xy_visualization/types.ts", - "lineNumber": 421 + "lineNumber": 424 }, "signature": [ { @@ -1154,7 +1178,7 @@ "description": [], "source": { "path": "x-pack/plugins/lens/public/xy_visualization/types.ts", - "lineNumber": 422 + "lineNumber": 425 }, "signature": [ "\"hide\" | \"inside\" | \"outside\" | undefined" @@ -1168,7 +1192,7 @@ "description": [], "source": { "path": "x-pack/plugins/lens/public/xy_visualization/types.ts", - "lineNumber": 423 + "lineNumber": 426 }, "signature": [ "\"None\" | \"Zero\" | \"Linear\" | \"Carry\" | \"Lookahead\" | undefined" @@ -1182,7 +1206,7 @@ "description": [], "source": { "path": "x-pack/plugins/lens/public/xy_visualization/types.ts", - "lineNumber": 424 + "lineNumber": 427 }, "signature": [ { @@ -1203,7 +1227,7 @@ "description": [], "source": { "path": "x-pack/plugins/lens/public/xy_visualization/types.ts", - "lineNumber": 425 + "lineNumber": 428 }, "signature": [ "string | undefined" @@ -1217,7 +1241,7 @@ "description": [], "source": { "path": "x-pack/plugins/lens/public/xy_visualization/types.ts", - "lineNumber": 426 + "lineNumber": 429 }, "signature": [ "string | undefined" @@ -1231,7 +1255,7 @@ "description": [], "source": { "path": "x-pack/plugins/lens/public/xy_visualization/types.ts", - "lineNumber": 427 + "lineNumber": 430 }, "signature": [ "string | undefined" @@ -1245,7 +1269,7 @@ "description": [], "source": { "path": "x-pack/plugins/lens/public/xy_visualization/types.ts", - "lineNumber": 428 + "lineNumber": 431 }, "signature": [ { @@ -1266,7 +1290,7 @@ "description": [], "source": { "path": "x-pack/plugins/lens/public/xy_visualization/types.ts", - "lineNumber": 429 + "lineNumber": 432 }, "signature": [ { @@ -1287,7 +1311,7 @@ "description": [], "source": { "path": "x-pack/plugins/lens/public/xy_visualization/types.ts", - "lineNumber": 430 + "lineNumber": 433 }, "signature": [ { @@ -1299,11 +1323,25 @@ }, " | undefined" ] + }, + { + "tags": [], + "id": "def-public.XYState.curveType", + "type": "CompoundType", + "label": "curveType", + "description": [], + "source": { + "path": "x-pack/plugins/lens/public/xy_visualization/types.ts", + "lineNumber": 434 + }, + "signature": [ + "\"LINEAR\" | \"CURVE_MONOTONE_X\" | undefined" + ] } ], "source": { "path": "x-pack/plugins/lens/public/xy_visualization/types.ts", - "lineNumber": 419 + "lineNumber": 422 }, "initialIsOpen": false } @@ -1318,10 +1356,10 @@ "description": [], "source": { "path": "x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/metrics.tsx", - "lineNumber": 127 + "lineNumber": 126 }, "signature": [ - "BaseIndexPatternColumn & { params?: { format?: { id: string; params?: { decimals: number; } | undefined; } | undefined; } | undefined; } & FieldBasedIndexPatternColumn & { operationType: \"avg\"; }" + "BaseIndexPatternColumn & { params?: { format?: { id: string; params?: { decimals: number; } | undefined; } | undefined; } | undefined; } & FieldBasedIndexPatternColumn & { operationType: \"average\"; }" ], "initialIsOpen": false }, @@ -1380,12 +1418,12 @@ "tags": [], "description": [], "source": { - "path": "x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/derivative.tsx", - "lineNumber": 35 + "path": "x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/differences.tsx", + "lineNumber": 37 }, "signature": [ "BaseIndexPatternColumn", - " & { params?: { format?: { id: string; params?: { decimals: number; } | undefined; } | undefined; } | undefined; } & ReferenceBasedIndexPatternColumn & { operationType: 'derivative'; }" + " & { params?: { format?: { id: string; params?: { decimals: number; } | undefined; } | undefined; } | undefined; } & ReferenceBasedIndexPatternColumn & { operationType: typeof OPERATION_NAME; }" ], "initialIsOpen": false }, @@ -1456,7 +1494,7 @@ "section": "def-public.DateHistogramIndexPatternColumn", "text": "DateHistogramIndexPatternColumn" }, - " | MetricColumn<\"min\"> | MetricColumn<\"max\"> | MetricColumn<\"avg\"> | ", + " | MetricColumn<\"min\"> | MetricColumn<\"max\"> | MetricColumn<\"average\"> | ", { "pluginId": "lens", "scope": "public", @@ -1475,7 +1513,7 @@ "description": [], "source": { "path": "x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/metrics.tsx", - "lineNumber": 129 + "lineNumber": 128 }, "signature": [ "BaseIndexPatternColumn & { params?: { format?: { id: string; params?: { decimals: number; } | undefined; } | undefined; } | undefined; } & FieldBasedIndexPatternColumn & { operationType: \"max\"; }" @@ -1490,7 +1528,7 @@ "description": [], "source": { "path": "x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/metrics.tsx", - "lineNumber": 130 + "lineNumber": 129 }, "signature": [ "BaseIndexPatternColumn & { params?: { format?: { id: string; params?: { decimals: number; } | undefined; } | undefined; } | undefined; } & FieldBasedIndexPatternColumn & { operationType: \"median\"; }" @@ -1505,7 +1543,7 @@ "description": [], "source": { "path": "x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/metrics.tsx", - "lineNumber": 128 + "lineNumber": 127 }, "signature": [ "BaseIndexPatternColumn & { params?: { format?: { id: string; params?: { decimals: number; } | undefined; } | undefined; } | undefined; } & FieldBasedIndexPatternColumn & { operationType: \"min\"; }" @@ -1541,7 +1579,7 @@ "lineNumber": 406 }, "signature": [ - "\"range\" | \"filters\" | \"count\" | \"max\" | \"min\" | \"date_histogram\" | \"sum\" | \"terms\" | \"avg\" | \"median\" | \"cumulative_sum\" | \"derivative\" | \"moving_average\" | \"counter_rate\" | \"cardinality\" | \"percentile\" | \"last_value\"" + "\"range\" | \"filters\" | \"count\" | \"max\" | \"min\" | \"date_histogram\" | \"sum\" | \"average\" | \"terms\" | \"median\" | \"cumulative_sum\" | \"moving_average\" | \"counter_rate\" | \"differences\" | \"unique_count\" | \"percentile\" | \"last_value\"" ], "initialIsOpen": false }, @@ -1598,7 +1636,7 @@ "description": [], "source": { "path": "x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/metrics.tsx", - "lineNumber": 126 + "lineNumber": 125 }, "signature": [ "BaseIndexPatternColumn & { params?: { format?: { id: string; params?: { decimals: number; } | undefined; } | undefined; } | undefined; } & FieldBasedIndexPatternColumn & { operationType: \"sum\"; }" diff --git a/api_docs/lists.json b/api_docs/lists.json index fe06ebe62ce232..fc935809cd7d86 100644 --- a/api_docs/lists.json +++ b/api_docs/lists.json @@ -3698,6 +3698,188 @@ "lineNumber": 48 }, "initialIsOpen": false + }, + { + "id": "def-server.UpdateExceptionListItemOptions", + "type": "Interface", + "label": "UpdateExceptionListItemOptions", + "description": [], + "tags": [], + "children": [ + { + "tags": [], + "id": "def-server.UpdateExceptionListItemOptions._version", + "type": "string", + "label": "_version", + "description": [], + "source": { + "path": "x-pack/plugins/lists/server/services/exception_lists/exception_list_client_types.ts", + "lineNumber": 144 + }, + "signature": [ + "string | undefined" + ] + }, + { + "tags": [], + "id": "def-server.UpdateExceptionListItemOptions.comments", + "type": "Array", + "label": "comments", + "description": [], + "source": { + "path": "x-pack/plugins/lists/server/services/exception_lists/exception_list_client_types.ts", + "lineNumber": 145 + }, + "signature": [ + "({ comment: string; } & { id?: string | undefined; })[]" + ] + }, + { + "tags": [], + "id": "def-server.UpdateExceptionListItemOptions.entries", + "type": "Array", + "label": "entries", + "description": [], + "source": { + "path": "x-pack/plugins/lists/server/services/exception_lists/exception_list_client_types.ts", + "lineNumber": 146 + }, + "signature": [ + "({ field: string; operator: \"excluded\" | \"included\"; type: \"match\"; value: string; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match_any\"; value: string[]; } | { field: string; list: { id: string; type: \"boolean\" | \"date\" | \"text\" | \"keyword\" | \"ip\" | \"long\" | \"double\" | \"date_nanos\" | \"geo_point\" | \"geo_shape\" | \"short\" | \"binary\" | \"date_range\" | \"ip_range\" | \"shape\" | \"integer\" | \"byte\" | \"float\" | \"double_range\" | \"float_range\" | \"half_float\" | \"integer_range\" | \"long_range\"; }; operator: \"excluded\" | \"included\"; type: \"list\"; } | { field: string; operator: \"excluded\" | \"included\"; type: \"exists\"; } | { entries: ({ field: string; operator: \"excluded\" | \"included\"; type: \"match\"; value: string; } | { field: string; operator: \"excluded\" | \"included\"; type: \"match_any\"; value: string[]; } | { field: string; operator: \"excluded\" | \"included\"; type: \"exists\"; })[]; field: string; type: \"nested\"; })[]" + ] + }, + { + "tags": [], + "id": "def-server.UpdateExceptionListItemOptions.id", + "type": "string", + "label": "id", + "description": [], + "source": { + "path": "x-pack/plugins/lists/server/services/exception_lists/exception_list_client_types.ts", + "lineNumber": 147 + }, + "signature": [ + "string | undefined" + ] + }, + { + "tags": [], + "id": "def-server.UpdateExceptionListItemOptions.itemId", + "type": "string", + "label": "itemId", + "description": [], + "source": { + "path": "x-pack/plugins/lists/server/services/exception_lists/exception_list_client_types.ts", + "lineNumber": 148 + }, + "signature": [ + "string | undefined" + ] + }, + { + "tags": [], + "id": "def-server.UpdateExceptionListItemOptions.namespaceType", + "type": "CompoundType", + "label": "namespaceType", + "description": [], + "source": { + "path": "x-pack/plugins/lists/server/services/exception_lists/exception_list_client_types.ts", + "lineNumber": 149 + }, + "signature": [ + "\"single\" | \"agnostic\"" + ] + }, + { + "tags": [], + "id": "def-server.UpdateExceptionListItemOptions.name", + "type": "string", + "label": "name", + "description": [], + "source": { + "path": "x-pack/plugins/lists/server/services/exception_lists/exception_list_client_types.ts", + "lineNumber": 150 + }, + "signature": [ + "string | undefined" + ] + }, + { + "tags": [], + "id": "def-server.UpdateExceptionListItemOptions.osTypes", + "type": "Array", + "label": "osTypes", + "description": [], + "source": { + "path": "x-pack/plugins/lists/server/services/exception_lists/exception_list_client_types.ts", + "lineNumber": 151 + }, + "signature": [ + "(\"windows\" | \"linux\" | \"macos\")[]" + ] + }, + { + "tags": [], + "id": "def-server.UpdateExceptionListItemOptions.description", + "type": "string", + "label": "description", + "description": [], + "source": { + "path": "x-pack/plugins/lists/server/services/exception_lists/exception_list_client_types.ts", + "lineNumber": 152 + }, + "signature": [ + "string | undefined" + ] + }, + { + "tags": [], + "id": "def-server.UpdateExceptionListItemOptions.meta", + "type": "Uncategorized", + "label": "meta", + "description": [], + "source": { + "path": "x-pack/plugins/lists/server/services/exception_lists/exception_list_client_types.ts", + "lineNumber": 153 + }, + "signature": [ + "object | undefined" + ] + }, + { + "tags": [], + "id": "def-server.UpdateExceptionListItemOptions.tags", + "type": "Array", + "label": "tags", + "description": [], + "source": { + "path": "x-pack/plugins/lists/server/services/exception_lists/exception_list_client_types.ts", + "lineNumber": 154 + }, + "signature": [ + "string[] | undefined" + ] + }, + { + "tags": [], + "id": "def-server.UpdateExceptionListItemOptions.type", + "type": "string", + "label": "type", + "description": [], + "source": { + "path": "x-pack/plugins/lists/server/services/exception_lists/exception_list_client_types.ts", + "lineNumber": 155 + }, + "signature": [ + "\"simple\" | undefined" + ] + } + ], + "source": { + "path": "x-pack/plugins/lists/server/services/exception_lists/exception_list_client_types.ts", + "lineNumber": 143 + }, + "initialIsOpen": false } ], "enums": [], diff --git a/api_docs/ml.json b/api_docs/ml.json index fa9bd613195a67..067ae4c0ea212f 100644 --- a/api_docs/ml.json +++ b/api_docs/ml.json @@ -1626,13 +1626,7 @@ "isRequired": false, "signature": [ "Record | undefined" ], "description": [], @@ -1700,210 +1694,6 @@ } ], "interfaces": [ - { - "id": "def-server.AnalysisConfig", - "type": "Interface", - "label": "AnalysisConfig", - "description": [], - "tags": [], - "children": [ - { - "tags": [], - "id": "def-server.AnalysisConfig.bucket_span", - "type": "string", - "label": "bucket_span", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", - "lineNumber": 48 - } - }, - { - "tags": [], - "id": "def-server.AnalysisConfig.categorization_field_name", - "type": "string", - "label": "categorization_field_name", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", - "lineNumber": 49 - }, - "signature": [ - "string | undefined" - ] - }, - { - "tags": [], - "id": "def-server.AnalysisConfig.categorization_filters", - "type": "Array", - "label": "categorization_filters", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", - "lineNumber": 50 - }, - "signature": [ - "string[] | undefined" - ] - }, - { - "tags": [], - "id": "def-server.AnalysisConfig.categorization_analyzer", - "type": "CompoundType", - "label": "categorization_analyzer", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", - "lineNumber": 51 - }, - "signature": [ - "string | object | undefined" - ] - }, - { - "tags": [], - "id": "def-server.AnalysisConfig.detectors", - "type": "Array", - "label": "detectors", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", - "lineNumber": 52 - }, - "signature": [ - { - "pluginId": "ml", - "scope": "common", - "docId": "kibMlPluginApi", - "section": "def-common.Detector", - "text": "Detector" - }, - "[]" - ] - }, - { - "tags": [], - "id": "def-server.AnalysisConfig.influencers", - "type": "Array", - "label": "influencers", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", - "lineNumber": 53 - }, - "signature": [ - "string[]" - ] - }, - { - "tags": [], - "id": "def-server.AnalysisConfig.latency", - "type": "number", - "label": "latency", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", - "lineNumber": 54 - }, - "signature": [ - "number | undefined" - ] - }, - { - "tags": [], - "id": "def-server.AnalysisConfig.multivariate_by_fields", - "type": "CompoundType", - "label": "multivariate_by_fields", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", - "lineNumber": 55 - }, - "signature": [ - "boolean | undefined" - ] - }, - { - "tags": [], - "id": "def-server.AnalysisConfig.summary_count_field_name", - "type": "string", - "label": "summary_count_field_name", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", - "lineNumber": 56 - }, - "signature": [ - "string | undefined" - ] - }, - { - "tags": [], - "id": "def-server.AnalysisConfig.per_partition_categorization", - "type": "Object", - "label": "per_partition_categorization", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", - "lineNumber": 57 - }, - "signature": [ - { - "pluginId": "ml", - "scope": "common", - "docId": "kibMlPluginApi", - "section": "def-common.PerPartitionCategorization", - "text": "PerPartitionCategorization" - }, - " | undefined" - ] - } - ], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", - "lineNumber": 47 - }, - "initialIsOpen": false - }, - { - "id": "def-server.AnalysisLimits", - "type": "Interface", - "label": "AnalysisLimits", - "description": [], - "tags": [], - "children": [ - { - "tags": [], - "id": "def-server.AnalysisLimits.categorization_examples_limit", - "type": "number", - "label": "categorization_examples_limit", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", - "lineNumber": 73 - }, - "signature": [ - "number | undefined" - ] - }, - { - "tags": [], - "id": "def-server.AnalysisLimits.model_memory_limit", - "type": "string", - "label": "model_memory_limit", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", - "lineNumber": 74 - } - } - ], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", - "lineNumber": 72 - }, - "initialIsOpen": false - }, { "id": "def-server.AnomaliesTableRecord", "type": "Interface", @@ -2692,48 +2482,6 @@ }, "initialIsOpen": false }, - { - "id": "def-server.ChunkingConfig", - "type": "Interface", - "label": "ChunkingConfig", - "description": [], - "tags": [], - "children": [ - { - "tags": [], - "id": "def-server.ChunkingConfig.mode", - "type": "CompoundType", - "label": "mode", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/datafeed.ts", - "lineNumber": 32 - }, - "signature": [ - "\"auto\" | \"off\" | \"manual\"" - ] - }, - { - "tags": [], - "id": "def-server.ChunkingConfig.time_span", - "type": "string", - "label": "time_span", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/datafeed.ts", - "lineNumber": 33 - }, - "signature": [ - "string | undefined" - ] - } - ], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/datafeed.ts", - "lineNumber": 31 - }, - "initialIsOpen": false - }, { "id": "def-server.CombinedJob", "type": "Interface", @@ -2771,13 +2519,7 @@ "lineNumber": 20 }, "signature": [ - { - "pluginId": "ml", - "scope": "common", - "docId": "kibMlPluginApi", - "section": "def-common.Datafeed", - "text": "Datafeed" - } + "Datafeed" ] } ], @@ -2841,128 +2583,72 @@ "initialIsOpen": false }, { - "id": "def-server.CustomRule", + "id": "def-server.CustomSettings", "type": "Interface", - "label": "CustomRule", + "label": "CustomSettings", "description": [], "tags": [], "children": [ { "tags": [], - "id": "def-server.CustomRule.actions", + "id": "def-server.CustomSettings.custom_urls", "type": "Array", - "label": "actions", + "label": "custom_urls", "description": [], "source": { "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", - "lineNumber": 91 + "lineNumber": 16 }, "signature": [ - "string[]" + { + "pluginId": "ml", + "scope": "common", + "docId": "kibMlPluginApi", + "section": "def-common.UrlConfig", + "text": "UrlConfig" + }, + "[] | undefined" ] }, { "tags": [], - "id": "def-server.CustomRule.scope", - "type": "Uncategorized", - "label": "scope", + "id": "def-server.CustomSettings.created_by", + "type": "CompoundType", + "label": "created_by", "description": [], "source": { "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", - "lineNumber": 92 + "lineNumber": 17 }, "signature": [ - "object | undefined" + { + "pluginId": "ml", + "scope": "common", + "docId": "kibMlPluginApi", + "section": "def-common.CREATED_BY_LABEL", + "text": "CREATED_BY_LABEL" + }, + " | undefined" ] }, { "tags": [], - "id": "def-server.CustomRule.conditions", - "type": "Array", - "label": "conditions", + "id": "def-server.CustomSettings.job_tags", + "type": "Object", + "label": "job_tags", "description": [], "source": { "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", - "lineNumber": 93 + "lineNumber": 18 }, "signature": [ - "any[]" + "{ [tag: string]: string; } | undefined" ] } ], "source": { "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", - "lineNumber": 90 - }, - "initialIsOpen": false - }, - { - "id": "def-server.CustomSettings", - "type": "Interface", - "label": "CustomSettings", - "description": [], - "tags": [], - "children": [ - { - "tags": [], - "id": "def-server.CustomSettings.custom_urls", - "type": "Array", - "label": "custom_urls", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", - "lineNumber": 15 - }, - "signature": [ - { - "pluginId": "ml", - "scope": "common", - "docId": "kibMlPluginApi", - "section": "def-common.UrlConfig", - "text": "UrlConfig" - }, - "[] | undefined" - ] - }, - { - "tags": [], - "id": "def-server.CustomSettings.created_by", - "type": "CompoundType", - "label": "created_by", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", - "lineNumber": 16 - }, - "signature": [ - { - "pluginId": "ml", - "scope": "common", - "docId": "kibMlPluginApi", - "section": "def-common.CREATED_BY_LABEL", - "text": "CREATED_BY_LABEL" - }, - " | undefined" - ] - }, - { - "tags": [], - "id": "def-server.CustomSettings.job_tags", - "type": "Object", - "label": "job_tags", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", - "lineNumber": 17 - }, - "signature": [ - "{ [tag: string]: string; } | undefined" - ] - } - ], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", - "lineNumber": 14 + "lineNumber": 15 }, "initialIsOpen": false }, @@ -3182,1065 +2868,223 @@ "initialIsOpen": false }, { - "id": "def-server.DataDescription", - "type": "Interface", - "label": "DataDescription", - "description": [], - "tags": [], - "children": [ - { - "tags": [], - "id": "def-server.DataDescription.format", - "type": "string", - "label": "format", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", - "lineNumber": 78 - }, - "signature": [ - "string | undefined" - ] - }, - { - "tags": [], - "id": "def-server.DataDescription.time_field", - "type": "string", - "label": "time_field", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", - "lineNumber": 79 - } - }, - { - "tags": [], - "id": "def-server.DataDescription.time_format", - "type": "string", - "label": "time_format", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", - "lineNumber": 80 - }, - "signature": [ - "string | undefined" - ] - } - ], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", - "lineNumber": 77 - }, - "initialIsOpen": false - }, - { - "id": "def-server.Datafeed", + "id": "def-server.DatafeedStats", "type": "Interface", - "label": "Datafeed", + "label": "DatafeedStats", "description": [], "tags": [], "children": [ { "tags": [], - "id": "def-server.Datafeed.datafeed_id", + "id": "def-server.DatafeedStats.datafeed_id", "type": "string", "label": "datafeed_id", "description": [], "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/datafeed.ts", - "lineNumber": 14 + "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/datafeed_stats.ts", + "lineNumber": 12 } }, { "tags": [], - "id": "def-server.Datafeed.aggregations", - "type": "Object", - "label": "aggregations", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/datafeed.ts", - "lineNumber": 15 - }, - "signature": [ - "Record | undefined" - ] - }, - { - "tags": [], - "id": "def-server.Datafeed.aggs", - "type": "Object", - "label": "aggs", + "id": "def-server.DatafeedStats.state", + "type": "Enum", + "label": "state", "description": [], "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/datafeed.ts", - "lineNumber": 16 + "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/datafeed_stats.ts", + "lineNumber": 13 }, "signature": [ - "Record | undefined" + { + "pluginId": "ml", + "scope": "common", + "docId": "kibMlPluginApi", + "section": "def-common.DATAFEED_STATE", + "text": "DATAFEED_STATE" + } ] }, { "tags": [], - "id": "def-server.Datafeed.chunking_config", + "id": "def-server.DatafeedStats.node", "type": "Object", - "label": "chunking_config", + "label": "node", "description": [], "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/datafeed.ts", - "lineNumber": 17 + "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/datafeed_stats.ts", + "lineNumber": 14 }, "signature": [ { "pluginId": "ml", "scope": "common", "docId": "kibMlPluginApi", - "section": "def-common.ChunkingConfig", - "text": "ChunkingConfig" - }, - " | undefined" + "section": "def-common.Node", + "text": "Node" + } ] }, { "tags": [], - "id": "def-server.Datafeed.frequency", + "id": "def-server.DatafeedStats.assignment_explanation", "type": "string", - "label": "frequency", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/datafeed.ts", - "lineNumber": 18 - }, - "signature": [ - "string | undefined" - ] - }, - { - "tags": [], - "id": "def-server.Datafeed.indices", - "type": "Array", - "label": "indices", + "label": "assignment_explanation", "description": [], "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/datafeed.ts", - "lineNumber": 19 - }, - "signature": [ - "string[]" - ] + "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/datafeed_stats.ts", + "lineNumber": 15 + } }, { "tags": [], - "id": "def-server.Datafeed.indexes", - "type": "Array", - "label": "indexes", + "id": "def-server.DatafeedStats.timing_stats", + "type": "Object", + "label": "timing_stats", "description": [], "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/datafeed.ts", - "lineNumber": 20 + "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/datafeed_stats.ts", + "lineNumber": 16 }, "signature": [ - "string[] | undefined" + "TimingStats" ] - }, + } + ], + "source": { + "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/datafeed_stats.ts", + "lineNumber": 11 + }, + "initialIsOpen": false + }, + { + "id": "def-server.ForecastsStats", + "type": "Interface", + "label": "ForecastsStats", + "description": [], + "tags": [], + "children": [ { "tags": [], - "id": "def-server.Datafeed.job_id", - "type": "string", - "label": "job_id", + "id": "def-server.ForecastsStats.total", + "type": "number", + "label": "total", "description": [], "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/datafeed.ts", - "lineNumber": 21 + "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job_stats.ts", + "lineNumber": 66 } }, { "tags": [], - "id": "def-server.Datafeed.query", - "type": "Uncategorized", - "label": "query", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/datafeed.ts", - "lineNumber": 22 - }, - "signature": [ - "object" - ] - }, - { - "tags": [], - "id": "def-server.Datafeed.query_delay", - "type": "string", - "label": "query_delay", + "id": "def-server.ForecastsStats.forecasted_jobs", + "type": "number", + "label": "forecasted_jobs", "description": [], "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/datafeed.ts", - "lineNumber": 23 - }, - "signature": [ - "string | undefined" - ] + "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job_stats.ts", + "lineNumber": 67 + } }, { "tags": [], - "id": "def-server.Datafeed.script_fields", - "type": "Object", - "label": "script_fields", + "id": "def-server.ForecastsStats.memory_bytes", + "type": "Any", + "label": "memory_bytes", "description": [], "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/datafeed.ts", - "lineNumber": 24 + "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job_stats.ts", + "lineNumber": 68 }, "signature": [ - "Record | undefined" + "any" ] }, { "tags": [], - "id": "def-server.Datafeed.runtime_mappings", - "type": "Object", - "label": "runtime_mappings", + "id": "def-server.ForecastsStats.records", + "type": "Any", + "label": "records", "description": [], "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/datafeed.ts", - "lineNumber": 25 - }, - "signature": [ - "Record | undefined" - ] - }, - { - "tags": [], - "id": "def-server.Datafeed.scroll_size", - "type": "number", - "label": "scroll_size", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/datafeed.ts", - "lineNumber": 26 - }, - "signature": [ - "number | undefined" - ] - }, - { - "tags": [], - "id": "def-server.Datafeed.delayed_data_check_config", - "type": "Uncategorized", - "label": "delayed_data_check_config", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/datafeed.ts", - "lineNumber": 27 - }, - "signature": [ - "object | undefined" - ] - }, - { - "tags": [], - "id": "def-server.Datafeed.indices_options", - "type": "Object", - "label": "indices_options", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/datafeed.ts", - "lineNumber": 28 - }, - "signature": [ - { - "pluginId": "ml", - "scope": "common", - "docId": "kibMlPluginApi", - "section": "def-common.IndicesOptions", - "text": "IndicesOptions" - }, - " | undefined" - ] - } - ], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/datafeed.ts", - "lineNumber": 13 - }, - "initialIsOpen": false - }, - { - "id": "def-server.DatafeedStats", - "type": "Interface", - "label": "DatafeedStats", - "description": [], - "tags": [], - "children": [ - { - "tags": [], - "id": "def-server.DatafeedStats.datafeed_id", - "type": "string", - "label": "datafeed_id", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/datafeed_stats.ts", - "lineNumber": 12 - } - }, - { - "tags": [], - "id": "def-server.DatafeedStats.state", - "type": "Enum", - "label": "state", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/datafeed_stats.ts", - "lineNumber": 13 - }, - "signature": [ - { - "pluginId": "ml", - "scope": "common", - "docId": "kibMlPluginApi", - "section": "def-common.DATAFEED_STATE", - "text": "DATAFEED_STATE" - } - ] - }, - { - "tags": [], - "id": "def-server.DatafeedStats.node", - "type": "Object", - "label": "node", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/datafeed_stats.ts", - "lineNumber": 14 - }, - "signature": [ - { - "pluginId": "ml", - "scope": "common", - "docId": "kibMlPluginApi", - "section": "def-common.Node", - "text": "Node" - } - ] - }, - { - "tags": [], - "id": "def-server.DatafeedStats.assignment_explanation", - "type": "string", - "label": "assignment_explanation", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/datafeed_stats.ts", - "lineNumber": 15 - } - }, - { - "tags": [], - "id": "def-server.DatafeedStats.timing_stats", - "type": "Object", - "label": "timing_stats", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/datafeed_stats.ts", - "lineNumber": 16 - }, - "signature": [ - "TimingStats" - ] - } - ], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/datafeed_stats.ts", - "lineNumber": 11 - }, - "initialIsOpen": false - }, - { - "id": "def-server.Detector", - "type": "Interface", - "label": "Detector", - "description": [], - "tags": [], - "children": [ - { - "tags": [], - "id": "def-server.Detector.by_field_name", - "type": "string", - "label": "by_field_name", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", - "lineNumber": 61 - }, - "signature": [ - "string | undefined" - ] - }, - { - "tags": [], - "id": "def-server.Detector.detector_description", - "type": "string", - "label": "detector_description", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", - "lineNumber": 62 - }, - "signature": [ - "string | undefined" - ] - }, - { - "tags": [], - "id": "def-server.Detector.detector_index", - "type": "number", - "label": "detector_index", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", - "lineNumber": 63 - }, - "signature": [ - "number | undefined" - ] - }, - { - "tags": [], - "id": "def-server.Detector.exclude_frequent", - "type": "string", - "label": "exclude_frequent", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", - "lineNumber": 64 - }, - "signature": [ - "string | undefined" - ] - }, - { - "tags": [], - "id": "def-server.Detector.field_name", - "type": "string", - "label": "field_name", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", - "lineNumber": 65 - }, - "signature": [ - "string | undefined" - ] - }, - { - "tags": [], - "id": "def-server.Detector.function", - "type": "string", - "label": "function", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", - "lineNumber": 66 - } - }, - { - "tags": [], - "id": "def-server.Detector.over_field_name", - "type": "string", - "label": "over_field_name", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", - "lineNumber": 67 - }, - "signature": [ - "string | undefined" - ] - }, - { - "tags": [], - "id": "def-server.Detector.partition_field_name", - "type": "string", - "label": "partition_field_name", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", - "lineNumber": 68 - }, - "signature": [ - "string | undefined" - ] - }, - { - "tags": [], - "id": "def-server.Detector.use_null", - "type": "CompoundType", - "label": "use_null", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", - "lineNumber": 69 - }, - "signature": [ - "boolean | undefined" - ] - }, - { - "tags": [], - "id": "def-server.Detector.custom_rules", - "type": "Array", - "label": "custom_rules", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", - "lineNumber": 70 - }, - "signature": [ - { - "pluginId": "ml", - "scope": "common", - "docId": "kibMlPluginApi", - "section": "def-common.CustomRule", - "text": "CustomRule" - }, - "[] | undefined" - ] - } - ], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", - "lineNumber": 60 - }, - "initialIsOpen": false - }, - { - "id": "def-server.ForecastsStats", - "type": "Interface", - "label": "ForecastsStats", - "description": [], - "tags": [], - "children": [ - { - "tags": [], - "id": "def-server.ForecastsStats.total", - "type": "number", - "label": "total", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job_stats.ts", - "lineNumber": 66 - } - }, - { - "tags": [], - "id": "def-server.ForecastsStats.forecasted_jobs", - "type": "number", - "label": "forecasted_jobs", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job_stats.ts", - "lineNumber": 67 - } - }, - { - "tags": [], - "id": "def-server.ForecastsStats.memory_bytes", - "type": "Any", - "label": "memory_bytes", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job_stats.ts", - "lineNumber": 68 - }, - "signature": [ - "any" - ] - }, - { - "tags": [], - "id": "def-server.ForecastsStats.records", - "type": "Any", - "label": "records", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job_stats.ts", - "lineNumber": 69 - }, - "signature": [ - "any" - ] - }, - { - "tags": [], - "id": "def-server.ForecastsStats.processing_time_ms", - "type": "Any", - "label": "processing_time_ms", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job_stats.ts", - "lineNumber": 70 - }, - "signature": [ - "any" - ] - }, - { - "tags": [], - "id": "def-server.ForecastsStats.status", - "type": "Any", - "label": "status", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job_stats.ts", - "lineNumber": 71 - }, - "signature": [ - "any" - ] - } - ], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job_stats.ts", - "lineNumber": 65 - }, - "initialIsOpen": false - }, - { - "id": "def-server.IndicesOptions", - "type": "Interface", - "label": "IndicesOptions", - "description": [], - "tags": [], - "children": [ - { - "tags": [], - "id": "def-server.IndicesOptions.expand_wildcards", - "type": "CompoundType", - "label": "expand_wildcards", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/datafeed.ts", - "lineNumber": 49 - }, - "signature": [ - "\"all\" | \"none\" | \"hidden\" | \"open\" | \"closed\" | undefined" - ] - }, - { - "tags": [], - "id": "def-server.IndicesOptions.ignore_unavailable", - "type": "CompoundType", - "label": "ignore_unavailable", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/datafeed.ts", - "lineNumber": 50 - }, - "signature": [ - "boolean | undefined" - ] - }, - { - "tags": [], - "id": "def-server.IndicesOptions.allow_no_indices", - "type": "CompoundType", - "label": "allow_no_indices", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/datafeed.ts", - "lineNumber": 51 - }, - "signature": [ - "boolean | undefined" - ] - }, - { - "tags": [], - "id": "def-server.IndicesOptions.ignore_throttled", - "type": "CompoundType", - "label": "ignore_throttled", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/datafeed.ts", - "lineNumber": 52 - }, - "signature": [ - "boolean | undefined" - ] - } - ], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/datafeed.ts", - "lineNumber": 48 - }, - "initialIsOpen": false - }, - { - "id": "def-server.Influencer", - "type": "Interface", - "label": "Influencer", - "description": [], - "tags": [], - "children": [ - { - "tags": [], - "id": "def-server.Influencer.influencer_field_name", - "type": "string", - "label": "influencer_field_name", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomalies.ts", - "lineNumber": 11 - } - }, - { - "tags": [], - "id": "def-server.Influencer.influencer_field_values", - "type": "Array", - "label": "influencer_field_values", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomalies.ts", - "lineNumber": 12 - }, - "signature": [ - "string[]" - ] - } - ], - "source": { - "path": "x-pack/plugins/ml/common/types/anomalies.ts", - "lineNumber": 10 - }, - "initialIsOpen": false - }, - { - "id": "def-server.Job", - "type": "Interface", - "label": "Job", - "description": [], - "tags": [], - "children": [ - { - "tags": [], - "id": "def-server.Job.job_id", - "type": "string", - "label": "job_id", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", - "lineNumber": 23 - } - }, - { - "tags": [], - "id": "def-server.Job.analysis_config", - "type": "Object", - "label": "analysis_config", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", - "lineNumber": 24 - }, - "signature": [ - { - "pluginId": "ml", - "scope": "common", - "docId": "kibMlPluginApi", - "section": "def-common.AnalysisConfig", - "text": "AnalysisConfig" - } - ] - }, - { - "tags": [], - "id": "def-server.Job.analysis_limits", - "type": "Object", - "label": "analysis_limits", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", - "lineNumber": 25 - }, - "signature": [ - { - "pluginId": "ml", - "scope": "common", - "docId": "kibMlPluginApi", - "section": "def-common.AnalysisLimits", - "text": "AnalysisLimits" - }, - " | undefined" - ] - }, - { - "tags": [], - "id": "def-server.Job.background_persist_interval", - "type": "string", - "label": "background_persist_interval", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", - "lineNumber": 26 - }, - "signature": [ - "string | undefined" - ] - }, - { - "tags": [], - "id": "def-server.Job.custom_settings", - "type": "Object", - "label": "custom_settings", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", - "lineNumber": 27 - }, - "signature": [ - { - "pluginId": "ml", - "scope": "common", - "docId": "kibMlPluginApi", - "section": "def-common.CustomSettings", - "text": "CustomSettings" - }, - " | undefined" - ] - }, - { - "tags": [], - "id": "def-server.Job.data_description", - "type": "Object", - "label": "data_description", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", - "lineNumber": 28 - }, - "signature": [ - { - "pluginId": "ml", - "scope": "common", - "docId": "kibMlPluginApi", - "section": "def-common.DataDescription", - "text": "DataDescription" - } - ] - }, - { - "tags": [], - "id": "def-server.Job.description", - "type": "string", - "label": "description", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", - "lineNumber": 29 - } - }, - { - "tags": [], - "id": "def-server.Job.groups", - "type": "Array", - "label": "groups", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", - "lineNumber": 30 - }, - "signature": [ - "string[]" - ] - }, - { - "tags": [], - "id": "def-server.Job.model_plot_config", - "type": "Object", - "label": "model_plot_config", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", - "lineNumber": 31 - }, - "signature": [ - { - "pluginId": "ml", - "scope": "common", - "docId": "kibMlPluginApi", - "section": "def-common.ModelPlotConfig", - "text": "ModelPlotConfig" - }, - " | undefined" - ] - }, - { - "tags": [], - "id": "def-server.Job.model_snapshot_retention_days", - "type": "number", - "label": "model_snapshot_retention_days", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", - "lineNumber": 32 - }, - "signature": [ - "number | undefined" - ] - }, - { - "tags": [], - "id": "def-server.Job.daily_model_snapshot_retention_after_days", - "type": "number", - "label": "daily_model_snapshot_retention_after_days", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", - "lineNumber": 33 - }, - "signature": [ - "number | undefined" - ] - }, - { - "tags": [], - "id": "def-server.Job.renormalization_window_days", - "type": "number", - "label": "renormalization_window_days", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", - "lineNumber": 34 - }, - "signature": [ - "number | undefined" - ] - }, - { - "tags": [], - "id": "def-server.Job.results_index_name", - "type": "string", - "label": "results_index_name", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", - "lineNumber": 35 - }, - "signature": [ - "string | undefined" - ] - }, - { - "tags": [], - "id": "def-server.Job.results_retention_days", - "type": "number", - "label": "results_retention_days", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", - "lineNumber": 36 - }, - "signature": [ - "number | undefined" - ] - }, - { - "tags": [], - "id": "def-server.Job.create_time", - "type": "number", - "label": "create_time", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", - "lineNumber": 39 - }, - "signature": [ - "number | undefined" - ] - }, - { - "tags": [], - "id": "def-server.Job.finished_time", - "type": "number", - "label": "finished_time", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", - "lineNumber": 40 + "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job_stats.ts", + "lineNumber": 69 }, "signature": [ - "number | undefined" + "any" ] }, { "tags": [], - "id": "def-server.Job.job_type", - "type": "string", - "label": "job_type", + "id": "def-server.ForecastsStats.processing_time_ms", + "type": "Any", + "label": "processing_time_ms", "description": [], "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", - "lineNumber": 41 + "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job_stats.ts", + "lineNumber": 70 }, "signature": [ - "\"anomaly_detector\" | undefined" + "any" ] }, { "tags": [], - "id": "def-server.Job.job_version", - "type": "string", - "label": "job_version", + "id": "def-server.ForecastsStats.status", + "type": "Any", + "label": "status", "description": [], "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", - "lineNumber": 42 + "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job_stats.ts", + "lineNumber": 71 }, "signature": [ - "string | undefined" + "any" ] - }, + } + ], + "source": { + "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job_stats.ts", + "lineNumber": 65 + }, + "initialIsOpen": false + }, + { + "id": "def-server.Influencer", + "type": "Interface", + "label": "Influencer", + "description": [], + "tags": [], + "children": [ { "tags": [], - "id": "def-server.Job.model_snapshot_id", + "id": "def-server.Influencer.influencer_field_name", "type": "string", - "label": "model_snapshot_id", + "label": "influencer_field_name", "description": [], "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", - "lineNumber": 43 - }, - "signature": [ - "string | undefined" - ] + "path": "x-pack/plugins/ml/common/types/anomalies.ts", + "lineNumber": 11 + } }, { "tags": [], - "id": "def-server.Job.deleting", - "type": "CompoundType", - "label": "deleting", + "id": "def-server.Influencer.influencer_field_values", + "type": "Array", + "label": "influencer_field_values", "description": [], "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", - "lineNumber": 44 + "path": "x-pack/plugins/ml/common/types/anomalies.ts", + "lineNumber": 12 }, "signature": [ - "boolean | undefined" + "string[]" ] } ], "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", - "lineNumber": 22 + "path": "x-pack/plugins/ml/common/types/anomalies.ts", + "lineNumber": 10 }, "initialIsOpen": false }, @@ -4790,62 +3634,6 @@ }, "initialIsOpen": false }, - { - "id": "def-server.ModelPlotConfig", - "type": "Interface", - "label": "ModelPlotConfig", - "description": [], - "tags": [], - "children": [ - { - "tags": [], - "id": "def-server.ModelPlotConfig.enabled", - "type": "CompoundType", - "label": "enabled", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", - "lineNumber": 84 - }, - "signature": [ - "boolean | undefined" - ] - }, - { - "tags": [], - "id": "def-server.ModelPlotConfig.annotations_enabled", - "type": "CompoundType", - "label": "annotations_enabled", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", - "lineNumber": 85 - }, - "signature": [ - "boolean | undefined" - ] - }, - { - "tags": [], - "id": "def-server.ModelPlotConfig.terms", - "type": "string", - "label": "terms", - "description": [], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", - "lineNumber": 86 - }, - "signature": [ - "string | undefined" - ] - } - ], - "source": { - "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", - "lineNumber": 83 - }, - "initialIsOpen": false - }, { "id": "def-server.ModelSizeStats", "type": "Interface", @@ -5298,7 +4086,7 @@ "description": [], "source": { "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", - "lineNumber": 97 + "lineNumber": 106 }, "signature": [ "boolean | undefined" @@ -5312,7 +4100,7 @@ "description": [], "source": { "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", - "lineNumber": 98 + "lineNumber": 107 }, "signature": [ "boolean | undefined" @@ -5321,7 +4109,7 @@ ], "source": { "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", - "lineNumber": 96 + "lineNumber": 105 }, "initialIsOpen": false } @@ -5336,13 +4124,43 @@ "description": [], "source": { "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/datafeed.ts", - "lineNumber": 36 + "lineNumber": 44 }, "signature": [ "{ [x: string]: { date_histogram: { field: string; fixed_interval: string;}; aggregations?: { [key: string]: any; } | undefined; aggs?: { [key: string]: any; } | undefined; }; }" ], "initialIsOpen": false }, + { + "id": "def-server.AnalysisConfig", + "type": "Type", + "label": "AnalysisConfig", + "tags": [], + "description": [], + "source": { + "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", + "lineNumber": 49 + }, + "signature": [ + "estypes.AnalysisConfig" + ], + "initialIsOpen": false + }, + { + "id": "def-server.AnalysisLimits", + "type": "Type", + "label": "AnalysisLimits", + "tags": [], + "description": [], + "source": { + "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", + "lineNumber": 77 + }, + "signature": [ + "estypes.AnalysisLimits" + ], + "initialIsOpen": false + }, { "id": "def-server.AnomalyResultType", "type": "Type", @@ -5366,13 +4184,73 @@ "description": [], "source": { "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", - "lineNumber": 12 + "lineNumber": 13 }, "signature": [ "string" ], "initialIsOpen": false }, + { + "id": "def-server.ChunkingConfig", + "type": "Type", + "label": "ChunkingConfig", + "tags": [], + "description": [], + "source": { + "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/datafeed.ts", + "lineNumber": 37 + }, + "signature": [ + "estypes.ChunkingConfig" + ], + "initialIsOpen": false + }, + { + "id": "def-server.CustomRule", + "type": "Type", + "label": "CustomRule", + "tags": [], + "description": [], + "source": { + "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", + "lineNumber": 97 + }, + "signature": [ + "estypes.DetectionRule" + ], + "initialIsOpen": false + }, + { + "id": "def-server.DataDescription", + "type": "Type", + "label": "DataDescription", + "tags": [], + "description": [], + "source": { + "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", + "lineNumber": 83 + }, + "signature": [ + "estypes.DataDescription" + ], + "initialIsOpen": false + }, + { + "id": "def-server.Datafeed", + "type": "Type", + "label": "Datafeed", + "tags": [], + "description": [], + "source": { + "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/datafeed.ts", + "lineNumber": 14 + }, + "signature": [ + "estypes.Datafeed" + ], + "initialIsOpen": false + }, { "id": "def-server.DatafeedId", "type": "Type", @@ -5381,7 +4259,7 @@ "description": [], "source": { "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/datafeed.ts", - "lineNumber": 11 + "lineNumber": 12 }, "signature": [ "string" @@ -5399,7 +4277,23 @@ "lineNumber": 14 }, "signature": [ - "Datafeed & DatafeedStats" + "Datafeed", + " & DatafeedStats" + ], + "initialIsOpen": false + }, + { + "id": "def-server.Detector", + "type": "Type", + "label": "Detector", + "tags": [], + "description": [], + "source": { + "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", + "lineNumber": 63 + }, + "signature": [ + "estypes.Detector" ], "initialIsOpen": false }, @@ -5418,6 +4312,36 @@ ], "initialIsOpen": false }, + { + "id": "def-server.IndicesOptions", + "type": "Type", + "label": "IndicesOptions", + "tags": [], + "description": [], + "source": { + "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/datafeed.ts", + "lineNumber": 56 + }, + "signature": [ + "estypes.IndicesOptions" + ], + "initialIsOpen": false + }, + { + "id": "def-server.Job", + "type": "Type", + "label": "Job", + "tags": [], + "description": [], + "source": { + "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", + "lineNumber": 23 + }, + "signature": [ + "estypes.Job" + ], + "initialIsOpen": false + }, { "id": "def-server.JobId", "type": "Type", @@ -5426,7 +4350,7 @@ "description": [], "source": { "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", - "lineNumber": 11 + "lineNumber": 12 }, "signature": [ "string" @@ -5444,7 +4368,8 @@ "lineNumber": 13 }, "signature": [ - "Job & JobStats" + "Job", + " & JobStats" ], "initialIsOpen": false }, @@ -5463,6 +4388,21 @@ ], "initialIsOpen": false }, + { + "id": "def-server.ModelPlotConfig", + "type": "Type", + "label": "ModelPlotConfig", + "tags": [], + "description": [], + "source": { + "path": "x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts", + "lineNumber": 90 + }, + "signature": [ + "estypes.ModelPlotConfig" + ], + "initialIsOpen": false + }, { "id": "def-server.ModuleSetupPayload", "type": "Type", diff --git a/api_docs/observability.json b/api_docs/observability.json index 439fd18db6469f..2128e27f0106f8 100644 --- a/api_docs/observability.json +++ b/api_docs/observability.json @@ -474,7 +474,7 @@ "description": [], "source": { "path": "x-pack/plugins/observability/public/hooks/use_fetcher.tsx", - "lineNumber": 30 + "lineNumber": 31 } }, { @@ -487,7 +487,7 @@ "description": [], "source": { "path": "x-pack/plugins/observability/public/hooks/use_fetcher.tsx", - "lineNumber": 31 + "lineNumber": 32 } }, { @@ -505,7 +505,7 @@ "description": [], "source": { "path": "x-pack/plugins/observability/public/hooks/use_fetcher.tsx", - "lineNumber": 33 + "lineNumber": 34 }, "signature": [ "boolean | undefined" @@ -514,7 +514,7 @@ ], "source": { "path": "x-pack/plugins/observability/public/hooks/use_fetcher.tsx", - "lineNumber": 32 + "lineNumber": 33 } } ], @@ -522,7 +522,7 @@ "returnComment": [], "source": { "path": "x-pack/plugins/observability/public/hooks/use_fetcher.tsx", - "lineNumber": 29 + "lineNumber": 30 }, "initialIsOpen": false }, @@ -1359,6 +1359,136 @@ }, "initialIsOpen": false }, + { + "id": "def-public.ObservabilityPublicPluginsSetup", + "type": "Interface", + "label": "ObservabilityPublicPluginsSetup", + "description": [], + "tags": [], + "children": [ + { + "tags": [], + "id": "def-public.ObservabilityPublicPluginsSetup.data", + "type": "Object", + "label": "data", + "description": [], + "source": { + "path": "x-pack/plugins/observability/public/plugin.ts", + "lineNumber": 30 + }, + "signature": [ + { + "pluginId": "data", + "scope": "public", + "docId": "kibDataPluginApi", + "section": "def-public.DataPublicPluginSetup", + "text": "DataPublicPluginSetup" + } + ] + }, + { + "tags": [], + "id": "def-public.ObservabilityPublicPluginsSetup.home", + "type": "Object", + "label": "home", + "description": [], + "source": { + "path": "x-pack/plugins/observability/public/plugin.ts", + "lineNumber": 31 + }, + "signature": [ + { + "pluginId": "home", + "scope": "public", + "docId": "kibHomePluginApi", + "section": "def-public.HomePublicPluginSetup", + "text": "HomePublicPluginSetup" + }, + " | undefined" + ] + } + ], + "source": { + "path": "x-pack/plugins/observability/public/plugin.ts", + "lineNumber": 29 + }, + "initialIsOpen": false + }, + { + "id": "def-public.ObservabilityPublicPluginsStart", + "type": "Interface", + "label": "ObservabilityPublicPluginsStart", + "description": [], + "tags": [], + "children": [ + { + "tags": [], + "id": "def-public.ObservabilityPublicPluginsStart.home", + "type": "Object", + "label": "home", + "description": [], + "source": { + "path": "x-pack/plugins/observability/public/plugin.ts", + "lineNumber": 35 + }, + "signature": [ + { + "pluginId": "home", + "scope": "public", + "docId": "kibHomePluginApi", + "section": "def-public.HomePublicPluginStart", + "text": "HomePublicPluginStart" + }, + " | undefined" + ] + }, + { + "tags": [], + "id": "def-public.ObservabilityPublicPluginsStart.data", + "type": "Object", + "label": "data", + "description": [], + "source": { + "path": "x-pack/plugins/observability/public/plugin.ts", + "lineNumber": 36 + }, + "signature": [ + { + "pluginId": "data", + "scope": "public", + "docId": "kibDataPluginApi", + "section": "def-public.DataPublicPluginStart", + "text": "DataPublicPluginStart" + } + ] + }, + { + "tags": [], + "id": "def-public.ObservabilityPublicPluginsStart.lens", + "type": "Object", + "label": "lens", + "description": [], + "source": { + "path": "x-pack/plugins/observability/public/plugin.ts", + "lineNumber": 37 + }, + "signature": [ + { + "pluginId": "lens", + "scope": "public", + "docId": "kibLensPluginApi", + "section": "def-public.LensPublicStart", + "text": "LensPublicStart" + } + ] + } + ], + "source": { + "path": "x-pack/plugins/observability/public/plugin.ts", + "lineNumber": 34 + }, + "initialIsOpen": false + }, { "id": "def-public.Series", "type": "Interface", @@ -1951,21 +2081,21 @@ ], "objects": [], "setup": { - "id": "def-public.ObservabilityPluginSetup", + "id": "def-public.ObservabilityPublicSetup", "type": "Interface", - "label": "ObservabilityPluginSetup", + "label": "ObservabilityPublicSetup", "description": [], "tags": [], "children": [ { "tags": [], - "id": "def-public.ObservabilityPluginSetup.dashboard", + "id": "def-public.ObservabilityPublicSetup.dashboard", "type": "Object", "label": "dashboard", "description": [], "source": { "path": "x-pack/plugins/observability/public/plugin.ts", - "lineNumber": 25 + "lineNumber": 26 }, "signature": [ "{ register: typeof ", @@ -1982,20 +2112,20 @@ ], "source": { "path": "x-pack/plugins/observability/public/plugin.ts", - "lineNumber": 24 + "lineNumber": 25 }, "lifecycle": "setup", "initialIsOpen": true }, "start": { - "id": "def-public.ObservabilityPluginStart", + "id": "def-public.ObservabilityPublicStart", "type": "Type", - "label": "ObservabilityPluginStart", + "label": "ObservabilityPublicStart", "tags": [], "description": [], "source": { "path": "x-pack/plugins/observability/public/plugin.ts", - "lineNumber": 33 + "lineNumber": 40 }, "signature": [ "void" @@ -2086,8 +2216,8 @@ "pluginId": "observability", "scope": "server", "docId": "kibObservabilityPluginApi", - "section": "def-server.MappingsDefinition", - "text": "MappingsDefinition" + "section": "def-server.Mappings", + "text": "Mappings" }, "; client: ", { @@ -2118,26 +2248,26 @@ "description": [], "source": { "path": "x-pack/plugins/observability/server/utils/create_or_update_index.ts", - "lineNumber": 32 + "lineNumber": 20 } }, { "tags": [], "id": "def-server.createOrUpdateIndex.{\n- index,\n mappings,\n client,\n logger,\n}.mappings", - "type": "Object", + "type": "CompoundType", "label": "mappings", "description": [], "source": { "path": "x-pack/plugins/observability/server/utils/create_or_update_index.ts", - "lineNumber": 33 + "lineNumber": 21 }, "signature": [ { "pluginId": "observability", "scope": "server", "docId": "kibObservabilityPluginApi", - "section": "def-server.MappingsDefinition", - "text": "MappingsDefinition" + "section": "def-server.Mappings", + "text": "Mappings" } ] }, @@ -2149,7 +2279,7 @@ "description": [], "source": { "path": "x-pack/plugins/observability/server/utils/create_or_update_index.ts", - "lineNumber": 34 + "lineNumber": 22 }, "signature": [ { @@ -2169,7 +2299,7 @@ "description": [], "source": { "path": "x-pack/plugins/observability/server/utils/create_or_update_index.ts", - "lineNumber": 35 + "lineNumber": 23 }, "signature": [ "Logger" @@ -2178,7 +2308,7 @@ ], "source": { "path": "x-pack/plugins/observability/server/utils/create_or_update_index.ts", - "lineNumber": 31 + "lineNumber": 19 } } ], @@ -2186,7 +2316,7 @@ "returnComment": [], "source": { "path": "x-pack/plugins/observability/server/utils/create_or_update_index.ts", - "lineNumber": 26 + "lineNumber": 14 }, "initialIsOpen": false }, @@ -2224,82 +2354,32 @@ "initialIsOpen": false } ], - "interfaces": [ + "interfaces": [], + "enums": [], + "misc": [ { - "id": "def-server.MappingsDefinition", - "type": "Interface", - "label": "MappingsDefinition", - "description": [], + "id": "def-server.Mappings", + "type": "Type", + "label": "Mappings", "tags": [], - "children": [ - { - "tags": [], - "id": "def-server.MappingsDefinition.dynamic", - "type": "CompoundType", - "label": "dynamic", - "description": [], - "source": { - "path": "x-pack/plugins/observability/server/utils/create_or_update_index.ts", - "lineNumber": 21 - }, - "signature": [ - "boolean | \"strict\" | undefined" - ] - }, - { - "tags": [], - "id": "def-server.MappingsDefinition.properties", - "type": "Object", - "label": "properties", - "description": [], - "source": { - "path": "x-pack/plugins/observability/server/utils/create_or_update_index.ts", - "lineNumber": 22 - }, - "signature": [ - "Record" - ] - }, - { - "tags": [], - "id": "def-server.MappingsDefinition.dynamic_templates", - "type": "Array", - "label": "dynamic_templates", - "description": [], - "source": { - "path": "x-pack/plugins/observability/server/utils/create_or_update_index.ts", - "lineNumber": 23 - }, - "signature": [ - "any[] | undefined" - ] - } - ], + "description": [], "source": { "path": "x-pack/plugins/observability/server/utils/create_or_update_index.ts", - "lineNumber": 20 + "lineNumber": 11 }, + "signature": [ + "TypeMapping", + " & { all_field?: ", + "AllField", + " | undefined; date_detection?: boolean | undefined; dynamic?: boolean | \"strict\" | undefined; dynamic_date_formats?: string[] | undefined; dynamic_templates?: Record | Record[] | undefined; field_names_field?: ", + "FieldNamesField" + ], "initialIsOpen": false - } - ], - "enums": [], - "misc": [ + }, { "id": "def-server.ObservabilityConfig", "type": "Type", @@ -2330,7 +2410,9 @@ "Annotation", "; }>; getById: (getByIdParams: { id: string; }) => Promise<", "GetResponse", - ">; delete: (deleteParams: { id: string; }) => Promise>; }" + ">; delete: (deleteParams: { id: string; }) => Promise<", + "DeleteResponse", + ">; }" ], "initialIsOpen": false } diff --git a/api_docs/observability.mdx b/api_docs/observability.mdx index 353e65b0fa0808..ef0e952f5161c7 100644 --- a/api_docs/observability.mdx +++ b/api_docs/observability.mdx @@ -42,9 +42,6 @@ import observabilityObj from './observability.json'; ### Classes -### Interfaces - - ### Consts, variables and types diff --git a/api_docs/reporting.json b/api_docs/reporting.json index 29d0d485452da4..be0c9638660387 100644 --- a/api_docs/reporting.json +++ b/api_docs/reporting.json @@ -1352,7 +1352,7 @@ "section": "def-server.SavedObjectsClient", "text": "SavedObjectsClient" }, - ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"errors\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\">) => Promise<", + ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"errors\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\" | \"createPointInTimeFinder\">) => Promise<", { "pluginId": "core", "scope": "server", @@ -1377,7 +1377,7 @@ "section": "def-server.SavedObjectsClient", "text": "SavedObjectsClient" }, - ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"errors\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\">" + ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"errors\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\" | \"createPointInTimeFinder\">" ], "description": [], "source": { diff --git a/api_docs/security.json b/api_docs/security.json index 8e1214654a82f2..938a043a555cf2 100644 --- a/api_docs/security.json +++ b/api_docs/security.json @@ -982,7 +982,7 @@ "lineNumber": 109 }, "signature": [ - "{ type: string; reason: string; caused_by: { type: string; reason: string; }; }[] | undefined" + "{ type: string; reason: string; caused_by?: { type: string; reason: string; } | undefined; }[] | undefined" ] } ], diff --git a/api_docs/security_solution.json b/api_docs/security_solution.json index ae208eb4facc74..ae85541ba6bfd6 100644 --- a/api_docs/security_solution.json +++ b/api_docs/security_solution.json @@ -52,7 +52,7 @@ "description": [], "source": { "path": "x-pack/plugins/security_solution/public/plugin.tsx", - "lineNumber": 75 + "lineNumber": 79 } } ], @@ -60,7 +60,7 @@ "returnComment": [], "source": { "path": "x-pack/plugins/security_solution/public/plugin.tsx", - "lineNumber": 75 + "lineNumber": 79 } }, { @@ -144,7 +144,7 @@ "description": [], "source": { "path": "x-pack/plugins/security_solution/public/plugin.tsx", - "lineNumber": 98 + "lineNumber": 103 } }, { @@ -163,7 +163,7 @@ "description": [], "source": { "path": "x-pack/plugins/security_solution/public/plugin.tsx", - "lineNumber": 98 + "lineNumber": 103 } } ], @@ -171,7 +171,7 @@ "returnComment": [], "source": { "path": "x-pack/plugins/security_solution/public/plugin.tsx", - "lineNumber": 98 + "lineNumber": 103 } }, { @@ -215,7 +215,7 @@ "description": [], "source": { "path": "x-pack/plugins/security_solution/public/plugin.tsx", - "lineNumber": 348 + "lineNumber": 353 } }, { @@ -234,7 +234,7 @@ "description": [], "source": { "path": "x-pack/plugins/security_solution/public/plugin.tsx", - "lineNumber": 348 + "lineNumber": 353 } } ], @@ -242,7 +242,7 @@ "returnComment": [], "source": { "path": "x-pack/plugins/security_solution/public/plugin.tsx", - "lineNumber": 348 + "lineNumber": 353 } }, { @@ -258,13 +258,13 @@ "returnComment": [], "source": { "path": "x-pack/plugins/security_solution/public/plugin.tsx", - "lineNumber": 393 + "lineNumber": 398 } } ], "source": { "path": "x-pack/plugins/security_solution/public/plugin.tsx", - "lineNumber": 72 + "lineNumber": 75 }, "initialIsOpen": false } @@ -607,7 +607,7 @@ "description": [], "source": { "path": "x-pack/plugins/security_solution/server/plugin.ts", - "lineNumber": 338 + "lineNumber": 339 } }, { @@ -626,7 +626,7 @@ "description": [], "source": { "path": "x-pack/plugins/security_solution/server/plugin.ts", - "lineNumber": 338 + "lineNumber": 339 } } ], @@ -634,7 +634,7 @@ "returnComment": [], "source": { "path": "x-pack/plugins/security_solution/server/plugin.ts", - "lineNumber": 338 + "lineNumber": 339 } }, { @@ -650,7 +650,7 @@ "returnComment": [], "source": { "path": "x-pack/plugins/security_solution/server/plugin.ts", - "lineNumber": 404 + "lineNumber": 405 } } ], @@ -1415,9 +1415,7 @@ "signature": [ "Error & Partial<", "ResponseError", - ", ", - "Context", - ">>" + ", unknown>>" ], "description": [], "source": { @@ -1429,9 +1427,7 @@ "signature": [ "(err: Error & Partial<", "ResponseError", - ", ", - "Context", - ">>) => ", + ", unknown>>) => ", "OutputError" ], "description": [], diff --git a/api_docs/telemetry.json b/api_docs/telemetry.json index 2d0108158e7e32..043e126de36407 100644 --- a/api_docs/telemetry.json +++ b/api_docs/telemetry.json @@ -334,7 +334,7 @@ "signature": [ "({ esClient }: ", "StatsCollectionConfig", - ") => Promise<{ clusterUuid: any; }[]>" + ") => Promise<{ clusterUuid: string; }[]>" ], "description": [ "\nGet the cluster uuids from the connected cluster." @@ -369,7 +369,7 @@ "description": [], "source": { "path": "src/plugins/telemetry/server/telemetry_collection/get_local_stats.ts", - "lineNumber": 59 + "lineNumber": 60 } }, { @@ -388,7 +388,7 @@ "description": [], "source": { "path": "src/plugins/telemetry/server/telemetry_collection/get_local_stats.ts", - "lineNumber": 60 + "lineNumber": 61 } }, { @@ -407,7 +407,7 @@ "description": [], "source": { "path": "src/plugins/telemetry/server/telemetry_collection/get_local_stats.ts", - "lineNumber": 61 + "lineNumber": 62 } } ], @@ -434,7 +434,7 @@ "label": "getLocalStats", "source": { "path": "src/plugins/telemetry/server/telemetry_collection/get_local_stats.ts", - "lineNumber": 58 + "lineNumber": 59 }, "tags": [], "returnComment": [], @@ -453,7 +453,7 @@ "section": "def-server.SavedObjectsClient", "text": "SavedObjectsClient" }, - ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"errors\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\">, uiSettingsClient: ", + ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"errors\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\" | \"createPointInTimeFinder\">, uiSettingsClient: ", { "pluginId": "core", "scope": "server", @@ -478,7 +478,7 @@ "section": "def-server.SavedObjectsClient", "text": "SavedObjectsClient" }, - ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"errors\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\">" + ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"errors\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\" | \"createPointInTimeFinder\">" ], "description": [], "source": { @@ -694,7 +694,7 @@ "description": [], "source": { "path": "src/plugins/telemetry/server/telemetry_collection/get_local_stats.ts", - "lineNumber": 50 + "lineNumber": 51 }, "signature": [ "{ timestamp: string; cluster_uuid: string; cluster_name: string; version: string; cluster_stats: any; collection: string; stack_stats: { data: DataTelemetryPayload | undefined; kibana: { count: number; indices: number; os: {}; versions: { version: string; count: number; }[]; plugins: { [plugin: string]: any; }; } | undefined; }; }" diff --git a/api_docs/telemetry_collection_manager.json b/api_docs/telemetry_collection_manager.json index 09e4b884bf809e..30d3bb76a43b20 100644 --- a/api_docs/telemetry_collection_manager.json +++ b/api_docs/telemetry_collection_manager.json @@ -105,7 +105,7 @@ "section": "def-server.SavedObjectsClient", "text": "SavedObjectsClient" }, - ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"errors\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\">" + ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"errors\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\" | \"createPointInTimeFinder\">" ] }, { diff --git a/api_docs/telemetry_collection_xpack.json b/api_docs/telemetry_collection_xpack.json index cf1b1a5998553b..8110cd6f2d27e2 100644 --- a/api_docs/telemetry_collection_xpack.json +++ b/api_docs/telemetry_collection_xpack.json @@ -11,167 +11,25 @@ "server": { "classes": [], "functions": [], - "interfaces": [ + "interfaces": [], + "enums": [], + "misc": [ { "id": "def-server.ESLicense", - "type": "Interface", + "type": "Type", "label": "ESLicense", - "description": [], "tags": [], - "children": [ - { - "tags": [], - "id": "def-server.ESLicense.status", - "type": "string", - "label": "status", - "description": [], - "source": { - "path": "x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/get_license.ts", - "lineNumber": 12 - } - }, - { - "tags": [], - "id": "def-server.ESLicense.uid", - "type": "string", - "label": "uid", - "description": [], - "source": { - "path": "x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/get_license.ts", - "lineNumber": 13 - } - }, - { - "tags": [], - "id": "def-server.ESLicense.hkey", - "type": "string", - "label": "hkey", - "description": [], - "source": { - "path": "x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/get_license.ts", - "lineNumber": 14 - } - }, - { - "tags": [], - "id": "def-server.ESLicense.type", - "type": "string", - "label": "type", - "description": [], - "source": { - "path": "x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/get_license.ts", - "lineNumber": 15 - } - }, - { - "tags": [], - "id": "def-server.ESLicense.issue_date", - "type": "string", - "label": "issue_date", - "description": [], - "source": { - "path": "x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/get_license.ts", - "lineNumber": 16 - } - }, - { - "tags": [], - "id": "def-server.ESLicense.issue_date_in_millis", - "type": "number", - "label": "issue_date_in_millis", - "description": [], - "source": { - "path": "x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/get_license.ts", - "lineNumber": 17 - } - }, - { - "tags": [], - "id": "def-server.ESLicense.expiry_date", - "type": "string", - "label": "expiry_date", - "description": [], - "source": { - "path": "x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/get_license.ts", - "lineNumber": 18 - } - }, - { - "tags": [], - "id": "def-server.ESLicense.expiry_date_in_millis", - "type": "number", - "label": "expiry_date_in_millis", - "description": [], - "source": { - "path": "x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/get_license.ts", - "lineNumber": 19 - } - }, - { - "tags": [], - "id": "def-server.ESLicense.max_nodes", - "type": "number", - "label": "max_nodes", - "description": [], - "source": { - "path": "x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/get_license.ts", - "lineNumber": 20 - } - }, - { - "tags": [], - "id": "def-server.ESLicense.issued_to", - "type": "string", - "label": "issued_to", - "description": [], - "source": { - "path": "x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/get_license.ts", - "lineNumber": 21 - } - }, - { - "tags": [], - "id": "def-server.ESLicense.issuer", - "type": "string", - "label": "issuer", - "description": [], - "source": { - "path": "x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/get_license.ts", - "lineNumber": 22 - } - }, - { - "tags": [], - "id": "def-server.ESLicense.start_date_in_millis", - "type": "number", - "label": "start_date_in_millis", - "description": [], - "source": { - "path": "x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/get_license.ts", - "lineNumber": 23 - } - }, - { - "tags": [], - "id": "def-server.ESLicense.max_resource_units", - "type": "number", - "label": "max_resource_units", - "description": [], - "source": { - "path": "x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/get_license.ts", - "lineNumber": 24 - } - } - ], + "description": [], "source": { "path": "x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/get_license.ts", - "lineNumber": 11 + "lineNumber": 10 }, + "signature": [ + "estypes.LicenseInformation" + ], "initialIsOpen": false } ], - "enums": [], - "misc": [], "objects": [] }, "common": { diff --git a/api_docs/telemetry_collection_xpack.mdx b/api_docs/telemetry_collection_xpack.mdx index dbebc1cde59ab0..058a9d3fcb460e 100644 --- a/api_docs/telemetry_collection_xpack.mdx +++ b/api_docs/telemetry_collection_xpack.mdx @@ -13,6 +13,6 @@ import telemetryCollectionXpackObj from './telemetry_collection_xpack.json'; ## Server -### Interfaces - +### Consts, variables and types + diff --git a/api_docs/vis_type_timeseries.json b/api_docs/vis_type_timeseries.json index 6f349cb3dfb936..907ced500294ac 100644 --- a/api_docs/vis_type_timeseries.json +++ b/api_docs/vis_type_timeseries.json @@ -37,7 +37,7 @@ { "pluginId": "data", "scope": "server", - "docId": "kibDataPluginApi", + "docId": "kibDataSearchPluginApi", "section": "def-server.DataRequestHandlerContext", "text": "DataRequestHandlerContext" }, diff --git a/docs/api/alerting.asciidoc b/docs/api/alerting.asciidoc new file mode 100644 index 00000000000000..ad2d358d17ba0a --- /dev/null +++ b/docs/api/alerting.asciidoc @@ -0,0 +1,47 @@ +[[alerting-apis]] +== Alerting APIs + +The following APIs are available for {kib} alerting. + +* <> to create a rule + +* <> to update the attributes for existing rules + +* <> to retrieve a single rule by ID + +* <> to permanently remove a rule + +* <> to retrieve a paginated set of rules by condition + +* <> to retrieve a list of rule types + +* <> to enable a single rule by ID + +* <> to disable a single rule by ID + +* <> to mute alert for a single rule by ID + +* <> to unmute alert for a single rule by ID + +* <> to mute all alerts for a single rule by ID + +* <> to unmute all alerts for a single rule by ID + +* <> to retrieve the health of the Alerting framework + +For deprecated APIs, refer to <>. + +include::alerting/create_rule.asciidoc[] +include::alerting/update_rule.asciidoc[] +include::alerting/get_rules.asciidoc[] +include::alerting/delete_rule.asciidoc[] +include::alerting/find_rules.asciidoc[] +include::alerting/list_rule_types.asciidoc[] +include::alerting/enable_rule.asciidoc[] +include::alerting/disable_rule.asciidoc[] +include::alerting/mute_all_alerts.asciidoc[] +include::alerting/mute_alert.asciidoc[] +include::alerting/unmute_all_alerts.asciidoc[] +include::alerting/unmute_alert.asciidoc[] +include::alerting/health.asciidoc[] +include::alerting/legacy/index.asciidoc[] diff --git a/docs/api/alerting/create_rule.asciidoc b/docs/api/alerting/create_rule.asciidoc new file mode 100644 index 00000000000000..01b6dfc40fcf6e --- /dev/null +++ b/docs/api/alerting/create_rule.asciidoc @@ -0,0 +1,196 @@ +[[create-rule-api]] +=== Create rule API +++++ +Create rule +++++ + +Create {kib} rules. + +[[create-rule-api-request]] +==== Request + +`POST :/api/alerting/rule/` + +`POST :/s//api/alerting/rule/` + +[[create-rule-api-path-params]] +==== Path parameters + +``:: + (Optional, string) Specifies a UUID v1 or v4 to use instead of a randomly generated ID. + +`space_id`:: + (Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. + +[[create-rule-api-request-body]] +==== Request body + +`name`:: + (Required, string) A name to reference and search. + +`tags`:: + (Optional, string array) A list of keywords to reference and search. + +`rule_type_id`:: + (Required, string) The ID of the rule type that you want to call when the rule is scheduled to run. + +`schedule`:: + (Required, object) The schedule specifying when this rule should be run, using one of the available schedule formats specified under ++ +._Schedule Formats_. +[%collapsible%open] +===== +A schedule is structured such that the key specifies the format you wish to use and its value specifies the schedule. + +We currently support the _Interval format_ which specifies the interval in seconds, minutes, hours or days at which the rule should execute. +Example: `{ interval: "10s" }`, `{ interval: "5m" }`, `{ interval: "1h" }`, `{ interval: "1d" }`. + +There are plans to support multiple other schedule formats in the near future. +===== + +`throttle`:: + (Optional, string) How often this rule should fire the same actions. This will prevent the rule from sending out the same notification over and over. For example, if a rule with a `schedule` of 1 minute stays in a triggered state for 90 minutes, setting a `throttle` of `10m` or `1h` will prevent it from sending 90 notifications during this period. + +`notify_when`:: + (Required, string) The condition for throttling the notification: `onActionGroupChange`, `onActiveAlert`, or `onThrottleInterval`. + +`enabled`:: + (Optional, boolean) Indicates if you want to run the rule on an interval basis after it is created. + +`consumer`:: + (Required, string) The name of the application that owns the rule. This name has to match the Kibana Feature name, as that dictates the required RBAC privileges. + +`params`:: + (Required, object) The parameters to pass to the rule type executor `params` value. This will also validate against the rule type params validator, if defined. + +`actions`:: + (Optional, object array) An array of the following action objects. ++ +.Properties of the action objects: +[%collapsible%open] +===== + `group`::: + (Required, string) Grouping actions is recommended for escalations for different types of alerts. If you don't need this, set this value to `default`. + + `id`::: + (Required, string) The ID of the connector saved object to execute. + + `params`::: + (Required, object) The map to the `params` that the <> will receive. ` params` are handled as Mustache templates and passed a default set of context. +===== + + +[[create-rule-api-request-codes]] +==== Response code + +`200`:: + Indicates a successful call. + +[[create-rule-api-example]] +==== Example + +[source,sh] +-------------------------------------------------- +$ curl -X POST api/alerting/rule -H 'kbn-xsrf: true' -H 'Content-Type: application/json' -d ' +{ + "params":{ + "aggType":"avg", + "termSize":6, + "thresholdComparator":">", + "timeWindowSize":5, + "timeWindowUnit":"m", + "groupBy":"top", + "threshold":[ + 1000 + ], + "index":[ + ".test-index" + ], + "timeField":"@timestamp", + "aggField":"sheet.version", + "termField":"name.keyword" + }, + "consumer":"alerts", + "rule_type_id":".index-threshold", + "schedule":{ + "interval":"1m" + }, + "actions":[ + { + "id":"dceeb5d0-6b41-11eb-802b-85b0c1bc8ba2", + "group":"threshold met", + "params":{ + "level":"info", + "message":"alert '{{alertName}}' is active for group '{{context.group}}':\n\n- Value: {{context.value}}\n- Conditions Met: {{context.conditions}} over {{params.timeWindowSize}}{{params.timeWindowUnit}}\n- Timestamp: {{context.date}}" + } + } + ], + "tags":[ + "cpu" + ], + "notify_when":"onActionGroupChange", + "name":"my alert" +}' +-------------------------------------------------- +// KIBANA + +The API returns the following: + +[source,sh] +-------------------------------------------------- +{ + "id": "41893910-6bca-11eb-9e0d-85d233e3ee35", + "notify_when": "onActionGroupChange", + "params": { + "aggType": "avg", + "termSize": 6, + "thresholdComparator": ">", + "timeWindowSize": 5, + "timeWindowUnit": "m", + "groupBy": "top", + "threshold": [ + 1000 + ], + "index": [ + ".kibana" + ], + "timeField": "@timestamp", + "aggField": "sheet.version", + "termField": "name.keyword" + }, + "consumer": "alerts", + "rule_type_id": ".index-threshold", + "schedule": { + "interval": "1m" + }, + "actions": [ + { + "connector_type_id": ".server-log", + "group": "threshold met", + "params": { + "level": "info", + "message": "alert {{alertName}} is active for group {{context.group}}:\n\n- Value: {{context.value}}\n- Conditions Met: {{context.conditions}} over {{params.timeWindowSize}}{{params.timeWindowUnit}}\n- Timestamp: {{context.date}}" + }, + "id": "dceeb5d0-6b41-11eb-802b-85b0c1bc8ba2" + } + ], + "tags": [ + "cpu" + ], + "name": "my alert", + "enabled": true, + "throttle": null, + "api_key_owner": "elastic", + "created_by": "elastic", + "updated_by": "elastic", + "mute_all": false, + "muted_alert_ids": [], + "updated_at": "2021-02-10T18:03:19.961Z", + "created_at": "2021-02-10T18:03:19.961Z", + "scheduled_task_id": "425b0800-6bca-11eb-9e0d-85d233e3ee35", + "execution_status": { + "last_execution_date": "2021-02-10T18:03:19.966Z", + "status": "pending" + } +} +-------------------------------------------------- diff --git a/docs/api/alerting/delete_rule.asciidoc b/docs/api/alerting/delete_rule.asciidoc new file mode 100644 index 00000000000000..29e642e04c9e2f --- /dev/null +++ b/docs/api/alerting/delete_rule.asciidoc @@ -0,0 +1,41 @@ +[[delete-rule-api]] +=== Delete rule API +++++ +Delete rule +++++ + +Permanently remove a rule. + +WARNING: Once you delete a rule, you cannot recover it. + +[[delete-rule-api-request]] +==== Request + +`DELETE :/api/alerting/rule/` + +`DELETE :/s//api/alerting/rule/` + +[[delete-rule-api-path-params]] +==== Path parameters + +`id`:: + (Required, string) The ID of the rule that you want to remove. + +`space_id`:: + (Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. + +[[delete-rule-api-response-codes]] +==== Response code + +`200`:: + Indicates a successful call. + +==== Example + +Delete a rule with ID: + +[source,sh] +-------------------------------------------------- +$ curl -X DELETE api/alerting/rule/41893910-6bca-11eb-9e0d-85d233e3ee35 +-------------------------------------------------- +// KIBANA diff --git a/docs/api/alerting/disable_rule.asciidoc b/docs/api/alerting/disable_rule.asciidoc new file mode 100644 index 00000000000000..ce003335623efb --- /dev/null +++ b/docs/api/alerting/disable_rule.asciidoc @@ -0,0 +1,39 @@ +[[disable-rule-api]] +=== Disable rule API +++++ +Disable rule +++++ + +Disable a rule. + +[[disable-rule-api-request]] +==== Request + +`POST :/api/alerting/rule//_disable` + +`POST :/s//api/alerting/rule//_disable` + +[[disable-rule-api-path-params]] +==== Path parameters + +`id`:: + (Required, string) The ID of the rule that you want to disable. + +`space_id`:: + (Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. + +[[disable-rule-api-response-codes]] +==== Response code + +`200`:: + Indicates a successful call. + +==== Example + +Disable a rule with ID: + +[source,sh] +-------------------------------------------------- +$ curl -X POST api/alerting/rule/41893910-6bca-11eb-9e0d-85d233e3ee35/_disable +-------------------------------------------------- +// KIBANA diff --git a/docs/api/alerting/enable_rule.asciidoc b/docs/api/alerting/enable_rule.asciidoc new file mode 100644 index 00000000000000..60f18b35109043 --- /dev/null +++ b/docs/api/alerting/enable_rule.asciidoc @@ -0,0 +1,39 @@ +[[enable-rule-api]] +=== Enable rule API +++++ +Enable rule +++++ + +Enable a rule. + +[[enable-rule-api-request]] +==== Request + +`POST :/api/alerting/rule//_enable` + +`POST :/s//api/alerting/rule//_enable` + +[[enable-rule-api-path-params]] +==== Path parameters + +`id`:: + (Required, string) The ID of the rule that you want to enable. + +`space_id`:: + (Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. + +[[enable-rule-api-response-codes]] +==== Response code + +`200`:: + Indicates a successful call. + +==== Example + +Enable a rule with ID: + +[source,sh] +-------------------------------------------------- +$ curl -X POST api/alerting/rule/41893910-6bca-11eb-9e0d-85d233e3ee35/_enable +-------------------------------------------------- +// KIBANA diff --git a/docs/api/alerting/find_rules.asciidoc b/docs/api/alerting/find_rules.asciidoc new file mode 100644 index 00000000000000..2df8b3522725cd --- /dev/null +++ b/docs/api/alerting/find_rules.asciidoc @@ -0,0 +1,127 @@ +[[find-rules-api]] +=== Find rules API +++++ +Find rules +++++ + +Retrieve a paginated set of rules based on condition. + +NOTE: As rules change in {kib}, the results on each page of the response also +change. Use the find API for traditional paginated results, but avoid using it to export large amounts of data. + +[[find-rules-api-request]] +==== Request + +`GET :/api/alerting/rules/_find` + +`GET :/s//api/alerting/rules/_find` + +[[find-rules-api-path-params]] +==== Path parameters + +`space_id`:: + (Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. + +[[find-rules-api-query-params]] +==== Query Parameters + +NOTE: Rule `params` are stored as a {ref}/flattened.html[flattened field type] and analyzed as keywords. + +`per_page`:: + (Optional, number) The number of rules to return per page. + +`page`:: + (Optional, number) The page number. + +`search`:: + (Optional, string) An Elasticsearch {ref}/query-dsl-simple-query-string-query.html[simple_query_string] query that filters the rules in the response. + +`default_search_operator`:: + (Optional, string) The operator to use for the `simple_query_string`. The default is 'OR'. + +`search_fields`:: + (Optional, array|string) The fields to perform the `simple_query_string` parsed query against. + +`fields`:: + (Optional, array|string) The fields to return in the `attributes` key of the response. + +`sort_field`:: + (Optional, string) Sorts the response. Could be a rule field returned in the `attributes` key of the response. + +`sort_order`:: + (Optional, string) Sort direction, either `asc` or `desc`. + +`has_reference`:: + (Optional, object) Filters the rules that have a relation with the reference objects with the specific "type" and "ID". + +`filter`:: + (Optional, string) A <> string that you filter with an attribute from your saved object. + It should look like savedObjectType.attributes.title: "myTitle". However, If you used a direct attribute of a saved object, such as `updatedAt`, + you will have to define your filter, for example, savedObjectType.updatedAt > 2018-12-22. + +[[find-rules-api-request-codes]] +==== Response code + +`200`:: + Indicates a successful call. + +==== Examples + +Find rules with names that start with `my`: + +[source,sh] +-------------------------------------------------- +$ curl -X GET api/alerting/rules/_find?search_fields=name&search=my* +-------------------------------------------------- +// KIBANA + +The API returns the following: + +[source,sh] +-------------------------------------------------- +{ + "page": 1, + "per_page": 10, + "total": 1, + "data": [ + { + "id": "0a037d60-6b62-11eb-9e0d-85d233e3ee35", + "notify_when": "onActionGroupChange", + "params": { + "aggType": "avg", + }, + "consumer": "alerts", + "rule_type_id": "test.rule.type", + "schedule": { + "interval": "1m" + }, + "actions": [], + "tags": [], + "name": "test rule", + "enabled": true, + "throttle": null, + "api_key_owner": "elastic", + "created_by": "elastic", + "updated_by": "elastic", + "mute_all": false, + "muted_alert_ids": [], + "updated_at": "2021-02-10T05:37:19.086Z", + "created_at": "2021-02-10T05:37:19.086Z", + "scheduled_task_id": "0b092d90-6b62-11eb-9e0d-85d233e3ee35", + "execution_status": { + "last_execution_date": "2021-02-10T17:55:14.262Z", + "status": "ok" + } + }, + ] +} +-------------------------------------------------- + +For parameters that accept multiple values (e.g. `fields`), repeat the +query parameter for each value: + +[source,sh] +-------------------------------------------------- +$ curl -X GET api/alerting/rules/_find?fields=id&fields=name +-------------------------------------------------- +// KIBANA diff --git a/docs/api/alerting/get_rules.asciidoc b/docs/api/alerting/get_rules.asciidoc new file mode 100644 index 00000000000000..1594ec1fb7ae60 --- /dev/null +++ b/docs/api/alerting/get_rules.asciidoc @@ -0,0 +1,75 @@ +[[get-rule-api]] +=== Get rule API +++++ +Get rule +++++ + +Retrieve a rule by ID. + +[[get-rule-api-request]] +==== Request + +`GET :/api/alerting/rule/` + +`GET :/s//api/alerting/rule/` + +[[get-rule-api-params]] +==== Path parameters + +`id`:: + (Required, string) The ID of the rule to retrieve. + +`space_id`:: + (Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. + +[[get-rule-api-codes]] +==== Response code + +`200`:: + Indicates a successful call. + +[[get-rule-api-example]] +==== Example + +Retrieve the rule object with the ID `41893910-6bca-11eb-9e0d-85d233e3ee35`: + +[source,sh] +-------------------------------------------------- +$ curl -X GET api/alerting/rule/41893910-6bca-11eb-9e0d-85d233e3ee35 +-------------------------------------------------- +// KIBANA + +The API returns the following: + +[source,sh] +-------------------------------------------------- +{ + "id": "0a037d60-6b62-11eb-9e0d-85d233e3ee35", + "notify_when": "onActionGroupChange", + "params": { + "aggType": "avg", + }, + "consumer": "alerts", + "rule_type_id": "test.rule.type", + "schedule": { + "interval": "1m" + }, + "actions": [], + "tags": [], + "name": "test rule", + "enabled": true, + "throttle": null, + "api_key_owner": "elastic", + "created_by": "elastic", + "updated_by": "elastic", + "mute_all": false, + "muted_alert_ids": [], + "updated_at": "2021-02-10T05:37:19.086Z", + "created_at": "2021-02-10T05:37:19.086Z", + "scheduled_task_id": "0b092d90-6b62-11eb-9e0d-85d233e3ee35", + "execution_status": { + "last_execution_date": "2021-02-10T17:55:14.262Z", + "status": "ok" + } +} +-------------------------------------------------- diff --git a/docs/api/alerting/health.asciidoc b/docs/api/alerting/health.asciidoc new file mode 100644 index 00000000000000..1e6b9ce22a981d --- /dev/null +++ b/docs/api/alerting/health.asciidoc @@ -0,0 +1,93 @@ +[[get-alerting-framework-health-api]] +=== Get Alerting framework health API +++++ +Get Alerting framework health +++++ + +Retrieve the health status of the Alerting framework. + +[[get-alerting-framework-health-api-request]] +==== Request + +`GET :/api/alerting/_health` + +`GET :/s//api/alerting/_health` + +[[get-alerting-framework-health-api-params]] +==== Path parameters + +`space_id`:: + (Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. + +[[get-alerting-framework-health-api-codes]] +==== Response code + +`200`:: + Indicates a successful call. + +[[get-alerting-framework-health-api-example]] +==== Example + +Retrieve the health status of the Alerting framework: + +[source,sh] +-------------------------------------------------- +$ curl -X GET api/alerting/_health +-------------------------------------------------- +// KIBANA + +The API returns the following: + +[source,sh] +-------------------------------------------------- +{ + "is_sufficiently_secure":true, + "has_permanent_encryption_key":true, + "alerting_framework_health":{ + "decryption_health":{ + "status":"ok", + "timestamp":"2021-02-10T23:35:04.949Z" + }, + "execution_health":{ + "status":"ok", + "timestamp":"2021-02-10T23:35:04.949Z" + }, + "read_health":{ + "status":"ok", + "timestamp":"2021-02-10T23:35:04.949Z" + } + } +} +-------------------------------------------------- + +The health API response contains the following properties: + +[cols="2*<"] +|=== + +| `is_sufficiently_secure` +| Returns `false` if security is enabled, but TLS is not. + +| `has_permanent_encryption_key` +| Return the state `false` if Encrypted Saved Object plugin has not a permanent encryption Key. + +| `alerting_framework_health` +| This state property has three substates that identify the health of the alerting framework API: `decryption_health`, `execution_health`, and `read_health`. + +|=== + +`alerting_framework_health` consists of the following properties: + +[cols="2*<"] +|=== + +| `decryption_health` +| Returns the timestamp and status of the rule decryption: `ok`, `warn` or `error` . + +| `execution_health` +| Returns the timestamp and status of the rule execution: `ok`, `warn` or `error`. + +| `read_health` +| Returns the timestamp and status of the rule reading events: `ok`, `warn` or `error`. + +|=== diff --git a/docs/api/alerts/create.asciidoc b/docs/api/alerting/legacy/create.asciidoc similarity index 97% rename from docs/api/alerts/create.asciidoc rename to docs/api/alerting/legacy/create.asciidoc index c3e6d36813972b..5c594d64a3f45b 100644 --- a/docs/api/alerts/create.asciidoc +++ b/docs/api/alerting/legacy/create.asciidoc @@ -1,9 +1,11 @@ [[alerts-api-create]] -=== Create alert API +=== Legacy create alert API ++++ -Create alert +Legacy create alert ++++ +WARNING: Deprecated in 7.13.0. Use <> instead. + Create {kib} alerts. [[alerts-api-create-request]] diff --git a/docs/api/alerts/delete.asciidoc b/docs/api/alerting/legacy/delete.asciidoc similarity index 86% rename from docs/api/alerts/delete.asciidoc rename to docs/api/alerting/legacy/delete.asciidoc index 72dfd5e87336cb..68851973cab5b9 100644 --- a/docs/api/alerts/delete.asciidoc +++ b/docs/api/alerting/legacy/delete.asciidoc @@ -1,9 +1,11 @@ [[alerts-api-delete]] -=== Delete alert API +=== Legacy delete alert API ++++ -Delete alert +Legacy delete alert ++++ +WARNING: Deprecated in 7.13.0. Use <> instead. + Permanently remove an alert. WARNING: Once you delete an alert, you cannot recover it. diff --git a/docs/api/alerts/disable.asciidoc b/docs/api/alerting/legacy/disable.asciidoc similarity index 85% rename from docs/api/alerts/disable.asciidoc rename to docs/api/alerting/legacy/disable.asciidoc index 86c58c37c2ecd1..56e06371570c2a 100644 --- a/docs/api/alerts/disable.asciidoc +++ b/docs/api/alerting/legacy/disable.asciidoc @@ -1,9 +1,11 @@ [[alerts-api-disable]] -=== Disable alert API +=== Legacy disable alert API ++++ -Disable alert +Legacy disable alert ++++ +WARNING: Deprecated in 7.13.0. Use <> instead. + Disable an alert. [[alerts-api-disable-request]] diff --git a/docs/api/alerts/enable.asciidoc b/docs/api/alerting/legacy/enable.asciidoc similarity index 85% rename from docs/api/alerts/enable.asciidoc rename to docs/api/alerting/legacy/enable.asciidoc index de1a5f7985a38a..913d96a84352bd 100644 --- a/docs/api/alerts/enable.asciidoc +++ b/docs/api/alerting/legacy/enable.asciidoc @@ -1,9 +1,11 @@ [[alerts-api-enable]] -=== Enable alert API +=== Legacy enable alert API ++++ -Enable alert +Legacy enable alert ++++ +WARNING: Deprecated in 7.13.0. Use <> instead. + Enable an alert. [[alerts-api-enable-request]] diff --git a/docs/api/alerts/find.asciidoc b/docs/api/alerting/legacy/find.asciidoc similarity index 96% rename from docs/api/alerts/find.asciidoc rename to docs/api/alerting/legacy/find.asciidoc index cc66d4e0f41834..94d9bc425bd214 100644 --- a/docs/api/alerts/find.asciidoc +++ b/docs/api/alerting/legacy/find.asciidoc @@ -1,9 +1,11 @@ [[alerts-api-find]] -=== Find alerts API +=== Legacy find alerts API ++++ -Find alerts +Legacy find alerts ++++ +WARNING: Deprecated in 7.13.0. Use <> instead. + Retrieve a paginated set of alerts based on condition. NOTE: As alerts change in {kib}, the results on each page of the response also diff --git a/docs/api/alerts/get.asciidoc b/docs/api/alerting/legacy/get.asciidoc similarity index 92% rename from docs/api/alerts/get.asciidoc rename to docs/api/alerting/legacy/get.asciidoc index 433605e8573325..f1014d18e87741 100644 --- a/docs/api/alerts/get.asciidoc +++ b/docs/api/alerting/legacy/get.asciidoc @@ -1,9 +1,11 @@ [[alerts-api-get]] -=== Get alert API +=== Legacy get alert API ++++ -Get alert +Legacy get alert ++++ +WARNING: Deprecated in 7.13.0. Use <> instead. + Retrieve an alert by ID. [[alerts-api-get-request]] diff --git a/docs/api/alerts/health.asciidoc b/docs/api/alerting/legacy/health.asciidoc similarity index 92% rename from docs/api/alerts/health.asciidoc rename to docs/api/alerting/legacy/health.asciidoc index b29e5def533849..b25307fb5efd18 100644 --- a/docs/api/alerts/health.asciidoc +++ b/docs/api/alerting/legacy/health.asciidoc @@ -1,9 +1,11 @@ [[alerts-api-health]] -=== Get Alerting framework health API +=== Legacy get Alerting framework health API ++++ -Get Alerting framework health +Legacy get Alerting framework health ++++ +WARNING: Deprecated in 7.13.0. Use <> instead. + Retrieve the health status of the Alerting framework. [[alerts-api-health-request]] diff --git a/docs/api/alerting/legacy/index.asciidoc b/docs/api/alerting/legacy/index.asciidoc new file mode 100644 index 00000000000000..cce2c378bdb581 --- /dev/null +++ b/docs/api/alerting/legacy/index.asciidoc @@ -0,0 +1,18 @@ +[[alerts-api]] +=== Deprecated 7.x APIs + +These APIs are deprecated and will be removed as of 8.0. + +include::create.asciidoc[leveloffset=+1] +include::delete.asciidoc[leveloffset=+1] +include::disable.asciidoc[leveloffset=+1] +include::enable.asciidoc[leveloffset=+1] +include::find.asciidoc[leveloffset=+1] +include::get.asciidoc[leveloffset=+1] +include::health.asciidoc[leveloffset=+1] +include::list.asciidoc[leveloffset=+1] +include::mute.asciidoc[leveloffset=+1] +include::mute_all.asciidoc[leveloffset=+1] +include::unmute.asciidoc[leveloffset=+1] +include::unmute_all.asciidoc[leveloffset=+1] +include::update.asciidoc[leveloffset=+1] \ No newline at end of file diff --git a/docs/api/alerts/list.asciidoc b/docs/api/alerting/legacy/list.asciidoc similarity index 96% rename from docs/api/alerts/list.asciidoc rename to docs/api/alerting/legacy/list.asciidoc index e180945accfd3e..e9ef3bbc27cd9f 100644 --- a/docs/api/alerts/list.asciidoc +++ b/docs/api/alerting/legacy/list.asciidoc @@ -1,9 +1,11 @@ [[alerts-api-list]] -=== List alert types API +=== Legacy list alert types API ++++ -List all alert types API +Legacy list all alert types ++++ +WARNING: Deprecated in 7.13.0. Use <> instead. + Retrieve a list of all alert types. [[alerts-api-list-request]] diff --git a/docs/api/alerts/mute.asciidoc b/docs/api/alerting/legacy/mute.asciidoc similarity index 87% rename from docs/api/alerts/mute.asciidoc rename to docs/api/alerting/legacy/mute.asciidoc index 84a2996b658386..dff42f5911e53f 100644 --- a/docs/api/alerts/mute.asciidoc +++ b/docs/api/alerting/legacy/mute.asciidoc @@ -1,9 +1,11 @@ [[alerts-api-mute]] -=== Mute alert instance API +=== Legacy mute alert instance API ++++ -Mute alert instance +Legacy mute alert instance ++++ +WARNING: Deprecated in 7.13.0. Use <> instead. + Mute an alert instance. [[alerts-api-mute-request]] diff --git a/docs/api/alerts/mute_all.asciidoc b/docs/api/alerting/legacy/mute_all.asciidoc similarity index 83% rename from docs/api/alerts/mute_all.asciidoc rename to docs/api/alerting/legacy/mute_all.asciidoc index 02f41eb3b768ec..df89fa15d15902 100644 --- a/docs/api/alerts/mute_all.asciidoc +++ b/docs/api/alerting/legacy/mute_all.asciidoc @@ -1,9 +1,11 @@ [[alerts-api-mute-all]] -=== Mute all alert instances API +=== Legacy mute all alert instances API ++++ -Mute all alert instances +Legacy mute all alert instances ++++ +WARNING: Deprecated in 7.13.0. Use <> instead. + Mute all alert instances. [[alerts-api-mute-all-request]] diff --git a/docs/api/alerts/unmute.asciidoc b/docs/api/alerting/legacy/unmute.asciidoc similarity index 87% rename from docs/api/alerts/unmute.asciidoc rename to docs/api/alerting/legacy/unmute.asciidoc index eb73bb539154f8..0be7e40dc1a198 100644 --- a/docs/api/alerts/unmute.asciidoc +++ b/docs/api/alerting/legacy/unmute.asciidoc @@ -1,9 +1,11 @@ [[alerts-api-unmute]] -=== Unmute alert instance API +=== Legacy unmute alert instance API ++++ -Unmute alert instance +Legacy unmute alert instance ++++ +WARNING: Deprecated in 7.13.0. Use <> instead. + Unmute an alert instance. [[alerts-api-unmute-request]] diff --git a/docs/api/alerts/unmute_all.asciidoc b/docs/api/alerting/legacy/unmute_all.asciidoc similarity index 83% rename from docs/api/alerts/unmute_all.asciidoc rename to docs/api/alerting/legacy/unmute_all.asciidoc index a20a20fd8204a4..8687c2d2fe8bb2 100644 --- a/docs/api/alerts/unmute_all.asciidoc +++ b/docs/api/alerting/legacy/unmute_all.asciidoc @@ -1,9 +1,11 @@ [[alerts-api-unmute-all]] -=== Unmute all alert instances API +=== Legacy unmute all alert instances API ++++ -Unmute all alert instances +Legacy unmute all alert instances ++++ +WARNING: Deprecated in 7.13.0. Use <> instead. + Unmute all alert instances. [[alerts-api-unmute-all-request]] diff --git a/docs/api/alerts/update.asciidoc b/docs/api/alerting/legacy/update.asciidoc similarity index 96% rename from docs/api/alerts/update.asciidoc rename to docs/api/alerting/legacy/update.asciidoc index a0b147ed4a15d1..bffdf26c314001 100644 --- a/docs/api/alerts/update.asciidoc +++ b/docs/api/alerting/legacy/update.asciidoc @@ -1,9 +1,11 @@ [[alerts-api-update]] -=== Update alert API +=== Legacy update alert API ++++ -Update alert +Legacy update alert ++++ +WARNING: Deprecated in 7.13.0. Use <> instead. + Update the attributes for an existing alert. [[alerts-api-update-request]] diff --git a/docs/api/alerting/list_rule_types.asciidoc b/docs/api/alerting/list_rule_types.asciidoc new file mode 100644 index 00000000000000..77ca8601a6e8ba --- /dev/null +++ b/docs/api/alerting/list_rule_types.asciidoc @@ -0,0 +1,135 @@ +[[list-rule-types-api]] +=== List rule types API +++++ +List rule types +++++ + +Retrieve a list of alerting rule types. + +[[list-rule-types-api-request]] +==== Request + +`GET :/api/alerting/rule_types` + +`GET :/s//api/alerting/rule_types` + +[[list-rule-types-api-params]] +==== Path parameters + +`space_id`:: + (Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. + +[[list-rule-types-api-codes]] +==== Response code + +`200`:: + Indicates a successful call. + +[[list-rule-types-api-example]] +==== Example + +[source,sh] +-------------------------------------------------- +$ curl -X GET api/alerting/rule_types +-------------------------------------------------- +// KIBANA + +The API returns the following: + +[source,sh] +-------------------------------------------------- +[ + { + "id":".index-threshold", + "name":"Index threshold", + "action_groups":[ + { + "id":"threshold met", + "name":"Threshold met" + }, + { + "id":"recovered", + "name":"Recovered" + } + ], + "recovery_action_group":{ + "id":"recovered", + "name":"Recovered" + }, + "default_action_group_id":"threshold met", + "action_variables":{ + "context":[ + { + "name":"message", + "description":"A pre-constructed message for the alert." + }, + ], + "state":[], + "params":[ + { + "name":"threshold", + "description":"An array of values to use as the threshold; 'between' and 'notBetween' require two values, the others require one." + }, + { + "name":"index", + "description":"index" + }, + ] + }, + "producer":"stackAlerts", + "minimum_license_required":"basic", + "enabled_in_license":true, + "authorized_consumers":{ + "alerts":{ + "read":true, + "all":true + }, + "stackAlerts":{ + "read":true, + "all":true + }, + "uptime":{ + "read":true, + "all":true + } + } + } +] +-------------------------------------------------- + +Each rule type contains the following properties: + +[cols="2*<"] +|=== + +| `name` +| The descriptive name of the rule type. + +| `id` +| The unique ID of the rule type. + +| `minimum_license_required` +| The license required to use the rule type. + +| `enabled_in_license` +| Whether the rule type is enabled or disabled based on the license. + +| `action_groups` +| An explicit list of groups for which the rule type can schedule actions, each with the action group's unique ID and human readable name. Rule `actions` validation will use this configuration to ensure that groups are valid. Use `kbn-i18n` to translate the names of the action group when registering the rule type. + +| `recovery_action_group` +| An action group to use when an alert goes from an active state, to an inactive one. Do not specify this action group under the `action_groups` property. If `recovery_action_group` is not specified, the default `recovered` action group is used. + +| `default_action_group_od` +| The default ID for the rule type group. + +| `action_variables` +| An explicit list of action variables that the rule type makes available via context and state in action parameter templates, and a short human readable description. The Rule UI will use this information to prompt users for these variables in action parameter editors. Use `kbn-i18n` to translate the descriptions. + +| `producer` +| The ID of the application producing this rule type. + +| `authorized_consumers` +| The list of the plugins IDs that have access to the rule type. + +|=== diff --git a/docs/api/alerting/mute_alert.asciidoc b/docs/api/alerting/mute_alert.asciidoc new file mode 100644 index 00000000000000..4ebf12d1ce10c6 --- /dev/null +++ b/docs/api/alerting/mute_alert.asciidoc @@ -0,0 +1,42 @@ +[[mute-alert-api]] +=== Mute alert API +++++ +Mute alert +++++ + +Mute an alert. + +[[mute-alert-api-request]] +==== Request + +`POST :/api/alerting/rule//alert//_mute` + +`POST :/s//api/alerting/rule//alert//_mute` + +[[mute-alert-api-path-params]] +==== Path parameters + +`rule_id`:: + (Required, string) The ID of the rule whose alert you want to mute. + +`alert_id`:: + (Required, string) The ID of the alert that you want to mute. + +`space_id`:: + (Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. + +[[mute-alert-api-response-codes]] +==== Response code + +`200`:: + Indicates a successful call. + +==== Example + +Mute alert with ID: + +[source,sh] +-------------------------------------------------- +$ curl -X POST api/alerting/rule/41893910-6bca-11eb-9e0d-85d233e3ee35/alert/dceeb5d0-6b41-11eb-802b-85b0c1bc8ba2/_mute +-------------------------------------------------- +// KIBANA diff --git a/docs/api/alerting/mute_all_alerts.asciidoc b/docs/api/alerting/mute_all_alerts.asciidoc new file mode 100644 index 00000000000000..58b6b14f49b4f4 --- /dev/null +++ b/docs/api/alerting/mute_all_alerts.asciidoc @@ -0,0 +1,39 @@ +[[mute-all-alerts-api]] +=== Mute all alerts API +++++ +Mute all alerts +++++ + +Mute all alerts. + +[[mute-all-alerts-api-request]] +==== Request + +`POST :/api/alerting/rule//_mute_all` + +`POST :/s//api/alerting/rule//_mute_all` + +[[mute-all-alerts-api-path-params]] +==== Path parameters + +`id`:: + (Required, string) The ID of the rule whose alerts you want to mute. + +`space_id`:: + (Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. + +[[mute-all-alerts-api-response-codes]] +==== Response code + +`200`:: + Indicates a successful call. + +==== Example + +Mute all alerts with ID: + +[source,sh] +-------------------------------------------------- +$ curl -X POST api/alerting/rule/41893910-6bca-11eb-9e0d-85d233e3ee35/_mute_all +-------------------------------------------------- +// KIBANA diff --git a/docs/api/alerting/unmute_alert.asciidoc b/docs/api/alerting/unmute_alert.asciidoc new file mode 100644 index 00000000000000..6e8870bb2fdaeb --- /dev/null +++ b/docs/api/alerting/unmute_alert.asciidoc @@ -0,0 +1,42 @@ +[[unmute-alert-api]] +=== Unmute alert API +++++ +Unmute alert +++++ + +Unmute an alert. + +[[unmute-alert-api-request]] +==== Request + +`POST :/api/alerting/rule//alert//_unmute` + +`POST :/s//api/alerting/rule//alert//_unmute` + +[[unmute-alert-api-path-params]] +==== Path parameters + +`rule_id`:: + (Required, string) The ID of the rule whose alert you want to mute. + +`alert_id`:: + (Required, string) The ID of the alert that you want to unmute. + +`space_id`:: + (Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. + +[[unmute-alert-api-response-codes]] +==== Response code + +`200`:: + Indicates a successful call. + +==== Example + +Unmute alert with ID: + +[source,sh] +-------------------------------------------------- +$ curl -X POST api/alerting/rule/41893910-6bca-11eb-9e0d-85d233e3ee35/alert/dceeb5d0-6b41-11eb-802b-85b0c1bc8ba2/_unmute +-------------------------------------------------- +// KIBANA diff --git a/docs/api/alerting/unmute_all_alerts.asciidoc b/docs/api/alerting/unmute_all_alerts.asciidoc new file mode 100644 index 00000000000000..c429ca288ae79c --- /dev/null +++ b/docs/api/alerting/unmute_all_alerts.asciidoc @@ -0,0 +1,39 @@ +[[unmute-all-alerts-api]] +=== Unmute all alerts API +++++ +Unmute all alerts +++++ + +Unmute all alerts. + +[[unmute-all-alerts-api-all-request]] +==== Request + +`POST :/api/alerting/rule//_unmute_all` + +`POST :/s//api/alerting/rule//_unmute_all` + +[[unmute-all-alerts-api-path-params]] +==== Path parameters + +`id`:: + (Required, string) The ID of the rule whose alerts you want to unmute. + +`space_id`:: + (Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. + +[[unmute-all-alerts-api-response-codes]] +==== Response code + +`200`:: + Indicates a successful call. + +==== Example + +Unmute all alerts with ID: + +[source,sh] +-------------------------------------------------- +$ curl -X POST api/alerting/rule/41893910-6bca-11eb-9e0d-85d233e3ee35/_unmute_all +-------------------------------------------------- +// KIBANA diff --git a/docs/api/alerting/update_rule.asciidoc b/docs/api/alerting/update_rule.asciidoc new file mode 100644 index 00000000000000..76c88a009be011 --- /dev/null +++ b/docs/api/alerting/update_rule.asciidoc @@ -0,0 +1,136 @@ +[[update-rule-api]] +=== Update rule API +++++ +Update rule +++++ + +Update the attributes for an existing rule. + +[[update-rule-api-request]] +==== Request + +`PUT :/api/alerting/rule/` + +`PUT :/s//api/alerting/rule/` + +[[update-rule-api-path-params]] +==== Path parameters + +`id`:: + (Required, string) The ID of the rule that you want to update. + +`space_id`:: + (Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. + +[[update-rule-api-request-body]] +==== Request body + +`name`:: + (Required, string) A name to reference and search. + +`tags`:: + (Optional, string array) A list of keywords to reference and search. + +`schedule`:: + (Required, object) When to run this rule. Use one of the available schedule formats. ++ +._Schedule Formats_. +[%collapsible%open] +===== +A schedule uses a key: value format. {kib} currently supports the _Interval format_ , which specifies the interval in seconds, minutes, hours, or days at which to execute the rule. + +Example: `{ interval: "10s" }`, `{ interval: "5m" }`, `{ interval: "1h" }`, `{ interval: "1d" }`. + +===== + +`throttle`:: + (Optional, string) How often this rule should fire the same actions. This will prevent the rule from sending out the same notification over and over. For example, if a rule with a `schedule` of 1 minute stays in a triggered state for 90 minutes, setting a `throttle` of `10m` or `1h` will prevent it from sending 90 notifications during this period. + +`notify_when`:: + (Required, string) The condition for throttling the notification: `onActionGroupChange`, `onActiveAlert`, or `onThrottleInterval`. + +`params`:: + (Required, object) The parameters to pass to the rule type executor `params` value. This will also validate against the rule type params validator, if defined. + +`actions`:: + (Optional, object array) An array of the following action objects. ++ +.Properties of the action objects: +[%collapsible%open] +===== + `group`::: + (Required, string) Grouping actions is recommended for escalations for different types of alerts. If you don't need this, set the value to `default`. + + `id`::: + (Required, string) The ID of the action that saved object executes. + + `params`::: + (Required, object) The map to the `params` that the <> will receive. `params` are handled as Mustache templates and passed a default set of context. +===== + + +[[update-rule-api-response-codes]] +==== Response code + +`200`:: + Indicates a successful call. + +[[update-rule-api-example]] +==== Example + +Update a rule with ID `ac4e6b90-6be7-11eb-ba0d-9b1c1f912d74` with a different name: + +[source,sh] +-------------------------------------------------- +$ curl -X PUT api/alerting/rule/ac4e6b90-6be7-11eb-ba0d-9b1c1f912d74 + +{ + "notify_when": "onActionGroupChange", + "params": { + "aggType": "avg", + }, + "schedule": { + "interval": "1m" + }, + "actions": [], + "tags": [], + "name": "new name", + "throttle": null, +} +-------------------------------------------------- +// KIBANA + +The API returns the following: + +[source,sh] +-------------------------------------------------- +{ + "id": "ac4e6b90-6be7-11eb-ba0d-9b1c1f912d74", + "notify_when": "onActionGroupChange", + "params": { + "aggType": "avg", + }, + "consumer": "alerts", + "rule_type_id": "test.rule.type", + "schedule": { + "interval": "1m" + }, + "actions": [], + "tags": [], + "name": "new name", + "enabled": true, + "throttle": null, + "api_key_owner": "elastic", + "created_by": "elastic", + "updated_by": "elastic", + "mute_all": false, + "muted_alert_ids": [], + "updated_at": "2021-02-10T05:37:19.086Z", + "created_at": "2021-02-10T05:37:19.086Z", + "scheduled_task_id": "0b092d90-6b62-11eb-9e0d-85d233e3ee35", + "execution_status": { + "last_execution_date": "2021-02-10T17:55:14.262Z", + "status": "ok" + } +} +-------------------------------------------------- diff --git a/docs/api/alerts.asciidoc b/docs/api/alerts.asciidoc deleted file mode 100644 index a19c538bcb4d7b..00000000000000 --- a/docs/api/alerts.asciidoc +++ /dev/null @@ -1,42 +0,0 @@ -[[alerts-api]] -== Alerts APIs - -The following APIs are available for managing {kib} alerts. - -* <> to create an alert - -* <> to update the attributes for existing alerts - -* <> to retrieve a single alert by ID - -* <> to permanently remove an alert - -* <> to retrieve a paginated set of alerts by condition - -* <> to retrieve a list of all alert types - -* <> to enable a single alert by ID - -* <> to disable a single alert by ID - -* <> to mute alert instances for a single alert by ID - -* <> to unmute alert instances for a single alert by ID - -* <> to unmute all alert instances for a single alert by ID - -* <> to retrieve the health of the alerts framework - -include::alerts/create.asciidoc[] -include::alerts/update.asciidoc[] -include::alerts/get.asciidoc[] -include::alerts/delete.asciidoc[] -include::alerts/find.asciidoc[] -include::alerts/list.asciidoc[] -include::alerts/enable.asciidoc[] -include::alerts/disable.asciidoc[] -include::alerts/mute_all.asciidoc[] -include::alerts/mute.asciidoc[] -include::alerts/unmute_all.asciidoc[] -include::alerts/unmute.asciidoc[] -include::alerts/health.asciidoc[] diff --git a/docs/user/api.asciidoc b/docs/user/api.asciidoc index c41f3d8a829e4b..6daf252c524ddd 100644 --- a/docs/user/api.asciidoc +++ b/docs/user/api.asciidoc @@ -99,7 +99,7 @@ include::{kib-repo-dir}/api/spaces-management.asciidoc[] include::{kib-repo-dir}/api/role-management.asciidoc[] include::{kib-repo-dir}/api/session-management.asciidoc[] include::{kib-repo-dir}/api/saved-objects.asciidoc[] -include::{kib-repo-dir}/api/alerts.asciidoc[] +include::{kib-repo-dir}/api/alerting.asciidoc[] include::{kib-repo-dir}/api/actions-and-connectors.asciidoc[] include::{kib-repo-dir}/api/dashboard-api.asciidoc[] include::{kib-repo-dir}/api/logstash-configuration-management.asciidoc[] diff --git a/x-pack/examples/alerting_example/public/components/view_alert.tsx b/x-pack/examples/alerting_example/public/components/view_alert.tsx index 8c942d685af27b..40eeb9fd360dca 100644 --- a/x-pack/examples/alerting_example/public/components/view_alert.tsx +++ b/x-pack/examples/alerting_example/public/components/view_alert.tsx @@ -21,8 +21,12 @@ import { import { withRouter, RouteComponentProps } from 'react-router-dom'; import { CoreStart } from 'kibana/public'; import { isEmpty } from 'lodash'; -import { Alert, AlertTaskState, BASE_ALERT_API_PATH } from '../../../../plugins/alerting/common'; import { ALERTING_EXAMPLE_APP_ID } from '../../common/constants'; +import { + Alert, + AlertTaskState, + LEGACY_BASE_ALERT_API_PATH, +} from '../../../../plugins/alerting/common'; type Props = RouteComponentProps & { http: CoreStart['http']; @@ -34,10 +38,10 @@ export const ViewAlertPage = withRouter(({ http, id }: Props) => { useEffect(() => { if (!alert) { - http.get(`${BASE_ALERT_API_PATH}/alert/${id}`).then(setAlert); + http.get(`${LEGACY_BASE_ALERT_API_PATH}/alert/${id}`).then(setAlert); } if (!alertState) { - http.get(`${BASE_ALERT_API_PATH}/alert/${id}/state`).then(setAlertState); + http.get(`${LEGACY_BASE_ALERT_API_PATH}/alert/${id}/state`).then(setAlertState); } }, [alert, alertState, http, id]); diff --git a/x-pack/examples/alerting_example/public/components/view_astros_alert.tsx b/x-pack/examples/alerting_example/public/components/view_astros_alert.tsx index 7e8487b0179fae..8eef1882b93899 100644 --- a/x-pack/examples/alerting_example/public/components/view_astros_alert.tsx +++ b/x-pack/examples/alerting_example/public/components/view_astros_alert.tsx @@ -23,8 +23,12 @@ import { import { withRouter, RouteComponentProps } from 'react-router-dom'; import { CoreStart } from 'kibana/public'; import { isEmpty } from 'lodash'; -import { Alert, AlertTaskState, BASE_ALERT_API_PATH } from '../../../../plugins/alerting/common'; import { ALERTING_EXAMPLE_APP_ID, AlwaysFiringParams } from '../../common/constants'; +import { + Alert, + AlertTaskState, + LEGACY_BASE_ALERT_API_PATH, +} from '../../../../plugins/alerting/common'; type Props = RouteComponentProps & { http: CoreStart['http']; @@ -40,10 +44,10 @@ export const ViewPeopleInSpaceAlertPage = withRouter(({ http, id }: Props) => { useEffect(() => { if (!alert) { - http.get(`${BASE_ALERT_API_PATH}/alert/${id}`).then(setAlert); + http.get(`${LEGACY_BASE_ALERT_API_PATH}/alert/${id}`).then(setAlert); } if (!alertState) { - http.get(`${BASE_ALERT_API_PATH}/alert/${id}/state`).then(setAlertState); + http.get(`${LEGACY_BASE_ALERT_API_PATH}/alert/${id}/state`).then(setAlertState); } }, [alert, alertState, http, id]); diff --git a/x-pack/plugins/alerting/README.md b/x-pack/plugins/alerting/README.md index 19322fed7363ea..eb64d71be565ea 100644 --- a/x-pack/plugins/alerting/README.md +++ b/x-pack/plugins/alerting/README.md @@ -453,20 +453,20 @@ The only case in which this handler will not be used to evaluate the navigation You can use the `registerNavigation` api to specify as many AlertType specific handlers as you like, but you can only use it once per AlertType as we wouldn't know which handler to use if you specified two for the same AlertType. For the same reason, you can only use `registerDefaultNavigation` once per plugin, as it covers all cases for your specific plugin. -## Experimental RESTful API +## Internal HTTP APIs -Using of the alert type requires you to create an alert that will contain parameters and actions for a given alert type. API description for CRUD operations is a part of the [user documentation](https://www.elastic.co/guide/en/kibana/master/alerts-api-update.html). -API listed below is experimental and could be changed or removed in the future. +Using of the rule type requires you to create a rule that will contain parameters and actions for a given rule type. API description for CRUD operations is a part of the [user documentation](https://www.elastic.co/guide/en/kibana/master/alerting-apis.html). +API listed below are internal and should not be consumed by plugin outside the alerting plugins. -### `GET /api/alerts/alert/{id}/state`: Get alert state +### `GET /internal/alerting/rule/{id}/state`: Get rule state Params: |Property|Description|Type| |---|---|---| -|id|The id of the alert whose state you're trying to get.|string| +|id|The id of the rule whose state you're trying to get.|string| -### `GET /api/alerts/alert/{id}/_instance_summary`: Get alert instance summary +### `GET /internal/alerting/rule/{id}/_alert_summary`: Get rule alert summary Similar to the `GET state` call, but collects additional information from the event log. @@ -475,7 +475,7 @@ Params: |Property|Description|Type| |---|---|---| -|id|The id of the alert whose instance summary you're trying to get.|string| +|id|The id of the rule whose alert summary you're trying to get.|string| Query: @@ -483,11 +483,11 @@ Query: |---|---|---| |dateStart|The date to start looking for alert events in the event log. Either an ISO date string, or a duration string indicating time since now.|string| -### `POST /api/alerts/alert/{id}/_update_api_key`: Update alert API key +### `POST /internal/alerting/rule/{id}/_update_api_key`: Update rule API key |Property|Description|Type| |---|---|---| -|id|The id of the alert you're trying to update the API key for. System will use user in request context to generate an API key for.|string| +|id|The id of the rule you're trying to update the API key for. System will use user in request context to generate an API key for.|string| ## Alert instance factory diff --git a/x-pack/plugins/alerting/common/index.ts b/x-pack/plugins/alerting/common/index.ts index ff1540090a357e..3530abb7384eac 100644 --- a/x-pack/plugins/alerting/common/index.ts +++ b/x-pack/plugins/alerting/common/index.ts @@ -24,5 +24,7 @@ export interface AlertingFrameworkHealth { alertingFrameworkHeath: AlertsHealth; } -export const BASE_ALERT_API_PATH = '/api/alerts'; +export const LEGACY_BASE_ALERT_API_PATH = '/api/alerts'; +export const BASE_ALERTING_API_PATH = '/api/alerting'; +export const INTERNAL_BASE_ALERTING_API_PATH = '/internal/alerting'; export const ALERTS_FEATURE_ID = 'alerts'; diff --git a/x-pack/plugins/alerting/public/alert_api.ts b/x-pack/plugins/alerting/public/alert_api.ts index 6eb2e29a7e8e57..d1213c80b95be1 100644 --- a/x-pack/plugins/alerting/public/alert_api.ts +++ b/x-pack/plugins/alerting/public/alert_api.ts @@ -7,11 +7,11 @@ import { HttpSetup } from 'kibana/public'; import { i18n } from '@kbn/i18n'; -import { BASE_ALERT_API_PATH } from '../common'; +import { LEGACY_BASE_ALERT_API_PATH } from '../common'; import type { Alert, AlertType } from '../common'; export async function loadAlertTypes({ http }: { http: HttpSetup }): Promise { - return await http.get(`${BASE_ALERT_API_PATH}/list_alert_types`); + return await http.get(`${LEGACY_BASE_ALERT_API_PATH}/list_alert_types`); } export async function loadAlertType({ @@ -22,7 +22,7 @@ export async function loadAlertType({ id: AlertType['id']; }): Promise { const maybeAlertType = ((await http.get( - `${BASE_ALERT_API_PATH}/list_alert_types` + `${LEGACY_BASE_ALERT_API_PATH}/list_alert_types` )) as AlertType[]).find((type) => type.id === id); if (!maybeAlertType) { throw new Error( @@ -44,5 +44,5 @@ export async function loadAlert({ http: HttpSetup; alertId: string; }): Promise { - return await http.get(`${BASE_ALERT_API_PATH}/alert/${alertId}`); + return await http.get(`${LEGACY_BASE_ALERT_API_PATH}/alert/${alertId}`); } diff --git a/x-pack/plugins/alerting/server/alerts_client/alerts_client.ts b/x-pack/plugins/alerting/server/alerts_client/alerts_client.ts index 1b29191d9063e0..e316ecd3c6fec6 100644 --- a/x-pack/plugins/alerting/server/alerts_client/alerts_client.ts +++ b/x-pack/plugins/alerting/server/alerts_client/alerts_client.ts @@ -125,7 +125,7 @@ interface IndexType { [key: string]: unknown; } -interface AggregateResult { +export interface AggregateResult { alertExecutionStatus: { [status: string]: number }; } @@ -157,7 +157,7 @@ export interface CreateOptions { }; } -interface UpdateOptions { +export interface UpdateOptions { id: string; data: { name: string; @@ -170,7 +170,7 @@ interface UpdateOptions { }; } -interface GetAlertInstanceSummaryParams { +export interface GetAlertInstanceSummaryParams { id: string; dateStart?: string; } @@ -229,7 +229,7 @@ export class AlertsClient { public async create({ data, options, - }: CreateOptions): Promise> { + }: CreateOptions): Promise> { const id = options?.id || SavedObjectsUtils.generateId(); try { diff --git a/x-pack/plugins/alerting/server/lib/errors/index.ts b/x-pack/plugins/alerting/server/lib/errors/index.ts new file mode 100644 index 00000000000000..9c6d233f15d3d0 --- /dev/null +++ b/x-pack/plugins/alerting/server/lib/errors/index.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ErrorThatHandlesItsOwnResponse } from './types'; + +export function isErrorThatHandlesItsOwnResponse( + e: ErrorThatHandlesItsOwnResponse +): e is ErrorThatHandlesItsOwnResponse { + return typeof (e as ErrorThatHandlesItsOwnResponse).sendResponse === 'function'; +} + +export { ErrorThatHandlesItsOwnResponse }; +export { AlertTypeDisabledError, AlertTypeDisabledReason } from './alert_type_disabled'; diff --git a/x-pack/plugins/alerting/server/lib/index.ts b/x-pack/plugins/alerting/server/lib/index.ts index 3fd0ac403f8f8a..493b0041069339 100644 --- a/x-pack/plugins/alerting/server/lib/index.ts +++ b/x-pack/plugins/alerting/server/lib/index.ts @@ -6,10 +6,17 @@ */ export { parseDuration, validateDurationSchema } from '../../common/parse_duration'; -export { LicenseState } from './license_state'; +export { ILicenseState, LicenseState } from './license_state'; export { validateAlertTypeParams } from './validate_alert_type_params'; export { getAlertNotifyWhenType } from './get_alert_notify_when_type'; +export { verifyApiAccess } from './license_api_access'; export { ErrorWithReason, getReasonFromError, isErrorWithReason } from './error_with_reason'; +export { + AlertTypeDisabledError, + AlertTypeDisabledReason, + ErrorThatHandlesItsOwnResponse, + isErrorThatHandlesItsOwnResponse, +} from './errors'; export { executionStatusFromState, executionStatusFromError, diff --git a/x-pack/plugins/alerting/server/plugin.ts b/x-pack/plugins/alerting/server/plugin.ts index ff36ebcd84ba52..787d3cc548ba13 100644 --- a/x-pack/plugins/alerting/server/plugin.ts +++ b/x-pack/plugins/alerting/server/plugin.ts @@ -37,25 +37,7 @@ import { } from '../../../../src/core/server'; import type { AlertingRequestHandlerContext } from './types'; -import { - aggregateAlertRoute, - createAlertRoute, - deleteAlertRoute, - findAlertRoute, - getAlertRoute, - getAlertStateRoute, - getAlertInstanceSummaryRoute, - listAlertTypesRoute, - updateAlertRoute, - enableAlertRoute, - disableAlertRoute, - updateApiKeyRoute, - muteAllAlertRoute, - unmuteAllAlertRoute, - muteAlertInstanceRoute, - unmuteAlertInstanceRoute, - healthRoute, -} from './routes'; +import { defineRoutes } from './routes'; import { LICENSE_TYPE, LicensingPluginSetup, LicensingPluginStart } from '../../licensing/server'; import { PluginSetupContract as ActionsPluginSetupContract, @@ -266,23 +248,7 @@ export class AlertingPlugin { // Routes const router = core.http.createRouter(); // Register routes - aggregateAlertRoute(router, this.licenseState); - createAlertRoute(router, this.licenseState); - deleteAlertRoute(router, this.licenseState); - findAlertRoute(router, this.licenseState); - getAlertRoute(router, this.licenseState); - getAlertStateRoute(router, this.licenseState); - getAlertInstanceSummaryRoute(router, this.licenseState); - listAlertTypesRoute(router, this.licenseState); - updateAlertRoute(router, this.licenseState); - enableAlertRoute(router, this.licenseState); - disableAlertRoute(router, this.licenseState); - updateApiKeyRoute(router, this.licenseState); - muteAllAlertRoute(router, this.licenseState); - unmuteAllAlertRoute(router, this.licenseState); - muteAlertInstanceRoute(router, this.licenseState); - unmuteAlertInstanceRoute(router, this.licenseState); - healthRoute(router, this.licenseState, plugins.encryptedSavedObjects); + defineRoutes(router, this.licenseState, plugins.encryptedSavedObjects); return { registerType< diff --git a/x-pack/plugins/alerting/server/routes/aggregate_rules.test.ts b/x-pack/plugins/alerting/server/routes/aggregate_rules.test.ts new file mode 100644 index 00000000000000..26c06eae33d0a2 --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/aggregate_rules.test.ts @@ -0,0 +1,150 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { aggregateRulesRoute } from './aggregate_rules'; +import { httpServiceMock } from 'src/core/server/mocks'; +import { licenseStateMock } from '../lib/license_state.mock'; +import { verifyApiAccess } from '../lib/license_api_access'; +import { mockHandlerArguments } from './_mock_handler_arguments'; +import { alertsClientMock } from '../alerts_client.mock'; + +const alertsClient = alertsClientMock.create(); + +jest.mock('../lib/license_api_access.ts', () => ({ + verifyApiAccess: jest.fn(), +})); + +beforeEach(() => { + jest.resetAllMocks(); +}); + +describe('aggregateRulesRoute', () => { + it('aggregate rules with proper parameters', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + aggregateRulesRoute(router, licenseState); + + const [config, handler] = router.get.mock.calls[0]; + + expect(config.path).toMatchInlineSnapshot(`"/internal/alerting/rules/_aggregate"`); + + const aggregateResult = { + alertExecutionStatus: { + ok: 15, + error: 2, + active: 23, + pending: 1, + unknown: 0, + }, + }; + alertsClient.aggregate.mockResolvedValueOnce(aggregateResult); + + const [context, req, res] = mockHandlerArguments( + { alertsClient }, + { + query: { + default_search_operator: 'AND', + }, + }, + ['ok'] + ); + + expect(await handler(context, req, res)).toMatchInlineSnapshot(` + Object { + "body": Object { + "rule_execution_status": Object { + "active": 23, + "error": 2, + "ok": 15, + "pending": 1, + "unknown": 0, + }, + }, + } + `); + + expect(alertsClient.aggregate).toHaveBeenCalledTimes(1); + expect(alertsClient.aggregate.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "options": Object { + "defaultSearchOperator": "AND", + }, + }, + ] + `); + + expect(res.ok).toHaveBeenCalledWith({ + body: { + rule_execution_status: { + ok: 15, + error: 2, + active: 23, + pending: 1, + unknown: 0, + }, + }, + }); + }); + + it('ensures the license allows aggregating rules', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + aggregateRulesRoute(router, licenseState); + + const [, handler] = router.get.mock.calls[0]; + + alertsClient.aggregate.mockResolvedValueOnce({ + alertExecutionStatus: { + ok: 15, + error: 2, + active: 23, + pending: 1, + unknown: 0, + }, + }); + + const [context, req, res] = mockHandlerArguments( + { alertsClient }, + { + query: { + default_search_operator: 'OR', + }, + } + ); + + await handler(context, req, res); + + expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); + }); + + it('ensures the license check prevents aggregating rules', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + (verifyApiAccess as jest.Mock).mockImplementation(() => { + throw new Error('OMG'); + }); + + aggregateRulesRoute(router, licenseState); + + const [, handler] = router.get.mock.calls[0]; + + const [context, req, res] = mockHandlerArguments( + {}, + { + query: {}, + }, + ['ok'] + ); + expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); + + expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); + }); +}); diff --git a/x-pack/plugins/alerting/server/routes/aggregate_rules.ts b/x-pack/plugins/alerting/server/routes/aggregate_rules.ts new file mode 100644 index 00000000000000..ecebd7851af6bc --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/aggregate_rules.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { IRouter } from 'kibana/server'; +import { schema } from '@kbn/config-schema'; +import { ILicenseState } from '../lib'; +import { AggregateResult, AggregateOptions } from '../alerts_client'; +import { RewriteResponseCase, RewriteRequestCase, verifyAccessAndContext } from './lib'; +import { AlertingRequestHandlerContext, INTERNAL_BASE_ALERTING_API_PATH } from '../types'; + +// config definition +const querySchema = schema.object({ + search: schema.maybe(schema.string()), + default_search_operator: schema.oneOf([schema.literal('OR'), schema.literal('AND')], { + defaultValue: 'OR', + }), + search_fields: schema.maybe(schema.arrayOf(schema.string())), + has_reference: schema.maybe( + // use nullable as maybe is currently broken + // in config-schema + schema.nullable( + schema.object({ + type: schema.string(), + id: schema.string(), + }) + ) + ), + filter: schema.maybe(schema.string()), +}); + +const rewriteQueryReq: RewriteRequestCase = ({ + default_search_operator: defaultSearchOperator, + has_reference: hasReference, + search_fields: searchFields, + ...rest +}) => ({ + ...rest, + defaultSearchOperator, + ...(hasReference ? { hasReference } : {}), + ...(searchFields ? { searchFields } : {}), +}); +const rewriteBodyRes: RewriteResponseCase = ({ + alertExecutionStatus, + ...rest +}) => ({ + ...rest, + rule_execution_status: alertExecutionStatus, +}); + +export const aggregateRulesRoute = ( + router: IRouter, + licenseState: ILicenseState +) => { + router.get( + { + path: `${INTERNAL_BASE_ALERTING_API_PATH}/rules/_aggregate`, + validate: { + query: querySchema, + }, + }, + router.handleLegacyErrors( + verifyAccessAndContext(licenseState, async function (context, req, res) { + const alertsClient = context.alerting.getAlertsClient(); + const options = rewriteQueryReq({ + ...req.query, + has_reference: req.query.has_reference || undefined, + }); + const aggregateResult = await alertsClient.aggregate({ options }); + return res.ok({ + body: rewriteBodyRes(aggregateResult), + }); + }) + ) + ); +}; diff --git a/x-pack/plugins/alerting/server/routes/create_rule.test.ts b/x-pack/plugins/alerting/server/routes/create_rule.test.ts new file mode 100644 index 00000000000000..5dbc5014ef6ba1 --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/create_rule.test.ts @@ -0,0 +1,298 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { pick } from 'lodash'; +import { createRuleRoute } from './create_rule'; +import { httpServiceMock } from 'src/core/server/mocks'; +import { licenseStateMock } from '../lib/license_state.mock'; +import { verifyApiAccess } from '../lib/license_api_access'; +import { mockHandlerArguments } from './_mock_handler_arguments'; +import { CreateOptions } from '../alerts_client'; +import { alertsClientMock } from '../alerts_client.mock'; +import { AlertTypeDisabledError } from '../lib'; +import { AsApiContract } from './lib'; +import { SanitizedAlert } from '../types'; + +const alertsClient = alertsClientMock.create(); + +jest.mock('../lib/license_api_access.ts', () => ({ + verifyApiAccess: jest.fn(), +})); + +beforeEach(() => { + jest.resetAllMocks(); +}); + +describe('createRuleRoute', () => { + const createdAt = new Date(); + const updatedAt = new Date(); + + const mockedAlert: SanitizedAlert<{ bar: boolean }> = { + alertTypeId: '1', + consumer: 'bar', + name: 'abc', + schedule: { interval: '10s' }, + tags: ['foo'], + params: { + bar: true, + }, + throttle: '30s', + actions: [ + { + actionTypeId: 'test', + group: 'default', + id: '2', + params: { + foo: true, + }, + }, + ], + enabled: true, + muteAll: false, + createdBy: '', + updatedBy: '', + apiKeyOwner: '', + mutedInstanceIds: [], + notifyWhen: 'onActionGroupChange', + createdAt, + updatedAt, + id: '123', + executionStatus: { + status: 'unknown', + lastExecutionDate: new Date('2020-08-20T19:23:38Z'), + }, + }; + + const ruleToCreate: AsApiContract['data']> = { + ...pick(mockedAlert, 'consumer', 'name', 'schedule', 'tags', 'params', 'throttle', 'enabled'), + rule_type_id: mockedAlert.alertTypeId, + notify_when: mockedAlert.notifyWhen, + actions: [ + { + group: mockedAlert.actions[0].group, + id: mockedAlert.actions[0].id, + params: mockedAlert.actions[0].params, + }, + ], + }; + + const createResult: AsApiContract> = { + ...ruleToCreate, + mute_all: mockedAlert.muteAll, + created_by: mockedAlert.createdBy, + updated_by: mockedAlert.updatedBy, + api_key_owner: mockedAlert.apiKeyOwner, + muted_alert_ids: mockedAlert.mutedInstanceIds, + created_at: mockedAlert.createdAt, + updated_at: mockedAlert.updatedAt, + id: mockedAlert.id, + execution_status: { + status: mockedAlert.executionStatus.status, + last_execution_date: mockedAlert.executionStatus.lastExecutionDate, + }, + actions: [ + { + ...ruleToCreate.actions[0], + connector_type_id: 'test', + }, + ], + }; + + it('creates a rule with proper parameters', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + createRuleRoute(router, licenseState); + + const [config, handler] = router.post.mock.calls[0]; + + expect(config.path).toMatchInlineSnapshot(`"/api/alerting/rule/{id?}"`); + + alertsClient.create.mockResolvedValueOnce(mockedAlert); + + const [context, req, res] = mockHandlerArguments( + { alertsClient }, + { + body: ruleToCreate, + }, + ['ok'] + ); + + expect(await handler(context, req, res)).toEqual({ body: createResult }); + + expect(alertsClient.create).toHaveBeenCalledTimes(1); + expect(alertsClient.create.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "data": Object { + "actions": Array [ + Object { + "group": "default", + "id": "2", + "params": Object { + "foo": true, + }, + }, + ], + "alertTypeId": "1", + "consumer": "bar", + "enabled": true, + "name": "abc", + "notifyWhen": "onActionGroupChange", + "params": Object { + "bar": true, + }, + "schedule": Object { + "interval": "10s", + }, + "tags": Array [ + "foo", + ], + "throttle": "30s", + }, + "options": Object { + "id": undefined, + }, + }, + ] + `); + + expect(res.ok).toHaveBeenCalledWith({ + body: createResult, + }); + }); + + it('allows providing a custom id', async () => { + const expectedResult = { + ...createResult, + id: 'custom-id', + }; + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + createRuleRoute(router, licenseState); + + const [config, handler] = router.post.mock.calls[0]; + + expect(config.path).toMatchInlineSnapshot(`"/api/alerting/rule/{id?}"`); + + alertsClient.create.mockResolvedValueOnce({ + ...mockedAlert, + id: 'custom-id', + }); + + const [context, req, res] = mockHandlerArguments( + { alertsClient }, + { + params: { id: 'custom-id' }, + body: ruleToCreate, + }, + ['ok'] + ); + + expect(await handler(context, req, res)).toEqual({ body: expectedResult }); + + expect(alertsClient.create).toHaveBeenCalledTimes(1); + expect(alertsClient.create.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "data": Object { + "actions": Array [ + Object { + "group": "default", + "id": "2", + "params": Object { + "foo": true, + }, + }, + ], + "alertTypeId": "1", + "consumer": "bar", + "enabled": true, + "name": "abc", + "notifyWhen": "onActionGroupChange", + "params": Object { + "bar": true, + }, + "schedule": Object { + "interval": "10s", + }, + "tags": Array [ + "foo", + ], + "throttle": "30s", + }, + "options": Object { + "id": "custom-id", + }, + }, + ] + `); + + expect(res.ok).toHaveBeenCalledWith({ + body: expectedResult, + }); + }); + + it('ensures the license allows creating rules', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + createRuleRoute(router, licenseState); + + const [, handler] = router.post.mock.calls[0]; + + alertsClient.create.mockResolvedValueOnce(mockedAlert); + + const [context, req, res] = mockHandlerArguments({ alertsClient }, { body: ruleToCreate }); + + await handler(context, req, res); + + expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); + }); + + it('ensures the license check prevents creating rules', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + (verifyApiAccess as jest.Mock).mockImplementation(() => { + throw new Error('OMG'); + }); + + createRuleRoute(router, licenseState); + + const [, handler] = router.post.mock.calls[0]; + + alertsClient.create.mockResolvedValueOnce(mockedAlert); + + const [context, req, res] = mockHandlerArguments({ alertsClient }, {}); + + expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); + + expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); + }); + + it('ensures the rule type gets validated for the license', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + createRuleRoute(router, licenseState); + + const [, handler] = router.post.mock.calls[0]; + + alertsClient.create.mockRejectedValue(new AlertTypeDisabledError('Fail', 'license_invalid')); + + const [context, req, res] = mockHandlerArguments({ alertsClient }, { body: ruleToCreate }, [ + 'ok', + 'forbidden', + ]); + + await handler(context, req, res); + + expect(res.forbidden).toHaveBeenCalledWith({ body: { message: 'Fail' } }); + }); +}); diff --git a/x-pack/plugins/alerting/server/routes/create_rule.ts b/x-pack/plugins/alerting/server/routes/create_rule.ts new file mode 100644 index 00000000000000..4e31db970ccc63 --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/create_rule.ts @@ -0,0 +1,141 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { IRouter } from 'kibana/server'; +import { schema } from '@kbn/config-schema'; +import { validateDurationSchema, ILicenseState, AlertTypeDisabledError } from '../lib'; +import { CreateOptions } from '../alerts_client'; +import { + RewriteRequestCase, + RewriteResponseCase, + handleDisabledApiKeysError, + verifyAccessAndContext, +} from './lib'; +import { + SanitizedAlert, + validateNotifyWhenType, + AlertTypeParams, + AlertingRequestHandlerContext, + BASE_ALERTING_API_PATH, + AlertNotifyWhenType, +} from '../types'; + +export const bodySchema = schema.object({ + name: schema.string(), + rule_type_id: schema.string(), + enabled: schema.boolean({ defaultValue: true }), + consumer: schema.string(), + tags: schema.arrayOf(schema.string(), { defaultValue: [] }), + throttle: schema.nullable(schema.string({ validate: validateDurationSchema })), + params: schema.recordOf(schema.string(), schema.any(), { defaultValue: {} }), + schedule: schema.object({ + interval: schema.string({ validate: validateDurationSchema }), + }), + actions: schema.arrayOf( + schema.object({ + group: schema.string(), + id: schema.string(), + params: schema.recordOf(schema.string(), schema.any(), { defaultValue: {} }), + }), + { defaultValue: [] } + ), + notify_when: schema.string({ validate: validateNotifyWhenType }), +}); + +const rewriteBodyReq: RewriteRequestCase['data']> = ({ + rule_type_id: alertTypeId, + notify_when: notifyWhen, + ...rest +}) => ({ + ...rest, + alertTypeId, + notifyWhen, +}); +const rewriteBodyRes: RewriteResponseCase> = ({ + actions, + alertTypeId, + scheduledTaskId, + createdBy, + updatedBy, + createdAt, + updatedAt, + apiKeyOwner, + notifyWhen, + muteAll, + mutedInstanceIds, + executionStatus: { lastExecutionDate, ...executionStatus }, + ...rest +}) => ({ + ...rest, + rule_type_id: alertTypeId, + scheduled_task_id: scheduledTaskId, + created_by: createdBy, + updated_by: updatedBy, + created_at: createdAt, + updated_at: updatedAt, + api_key_owner: apiKeyOwner, + notify_when: notifyWhen, + mute_all: muteAll, + muted_alert_ids: mutedInstanceIds, + execution_status: { + ...executionStatus, + last_execution_date: lastExecutionDate, + }, + actions: actions.map(({ group, id, actionTypeId, params }) => ({ + group, + id, + params, + connector_type_id: actionTypeId, + })), +}); + +export const createRuleRoute = ( + router: IRouter, + licenseState: ILicenseState +) => { + router.post( + { + path: `${BASE_ALERTING_API_PATH}/rule/{id?}`, + validate: { + params: schema.maybe( + schema.object({ + id: schema.maybe(schema.string()), + }) + ), + body: bodySchema, + }, + }, + handleDisabledApiKeysError( + router.handleLegacyErrors( + verifyAccessAndContext(licenseState, async function (context, req, res) { + const alertsClient = context.alerting.getAlertsClient(); + const rule = req.body; + const params = req.params; + try { + const createdRule: SanitizedAlert = await alertsClient.create( + { + data: rewriteBodyReq({ + ...rule, + notify_when: rule.notify_when as AlertNotifyWhenType, + }), + options: { id: params?.id }, + } + ); + return res.ok({ + body: rewriteBodyRes(createdRule), + }); + } catch (e) { + if (e instanceof AlertTypeDisabledError) { + return e.sendResponse(res); + } + throw e; + } + }) + ) + ) + ); +}; diff --git a/x-pack/plugins/alerting/server/routes/delete_rule.test.ts b/x-pack/plugins/alerting/server/routes/delete_rule.test.ts new file mode 100644 index 00000000000000..16d344548fc25f --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/delete_rule.test.ts @@ -0,0 +1,109 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { deleteRuleRoute } from './delete_rule'; +import { httpServiceMock } from 'src/core/server/mocks'; +import { licenseStateMock } from '../lib/license_state.mock'; +import { verifyApiAccess } from '../lib/license_api_access'; +import { mockHandlerArguments } from './_mock_handler_arguments'; +import { alertsClientMock } from '../alerts_client.mock'; + +const alertsClient = alertsClientMock.create(); + +jest.mock('../lib/license_api_access.ts', () => ({ + verifyApiAccess: jest.fn(), +})); + +beforeEach(() => { + jest.resetAllMocks(); +}); + +describe('deleteRuleRoute', () => { + it('deletes an alert with proper parameters', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + deleteRuleRoute(router, licenseState); + + const [config, handler] = router.delete.mock.calls[0]; + + expect(config.path).toMatchInlineSnapshot(`"/api/alerting/rule/{id}"`); + + alertsClient.delete.mockResolvedValueOnce({}); + + const [context, req, res] = mockHandlerArguments( + { alertsClient }, + { + params: { + id: '1', + }, + }, + ['noContent'] + ); + + expect(await handler(context, req, res)).toEqual(undefined); + + expect(alertsClient.delete).toHaveBeenCalledTimes(1); + expect(alertsClient.delete.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "id": "1", + }, + ] + `); + + expect(res.noContent).toHaveBeenCalled(); + }); + + it('ensures the license allows deleting rules', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + deleteRuleRoute(router, licenseState); + + const [, handler] = router.delete.mock.calls[0]; + + alertsClient.delete.mockResolvedValueOnce({}); + + const [context, req, res] = mockHandlerArguments( + { alertsClient }, + { + params: { id: '1' }, + } + ); + + await handler(context, req, res); + + expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); + }); + + it('ensures the license check prevents deleting rules', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + (verifyApiAccess as jest.Mock).mockImplementation(() => { + throw new Error('OMG'); + }); + + deleteRuleRoute(router, licenseState); + + const [, handler] = router.delete.mock.calls[0]; + + alertsClient.delete.mockResolvedValueOnce({}); + + const [context, req, res] = mockHandlerArguments( + { alertsClient }, + { + id: '1', + } + ); + + expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); + + expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); + }); +}); diff --git a/x-pack/plugins/alerting/server/routes/delete_rule.ts b/x-pack/plugins/alerting/server/routes/delete_rule.ts new file mode 100644 index 00000000000000..724eb5352ed238 --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/delete_rule.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { IRouter } from 'kibana/server'; +import { schema } from '@kbn/config-schema'; +import { ILicenseState } from '../lib'; +import { verifyAccessAndContext } from './lib'; +import { AlertingRequestHandlerContext, BASE_ALERTING_API_PATH } from '../types'; + +const paramSchema = schema.object({ + id: schema.string(), +}); + +export const deleteRuleRoute = ( + router: IRouter, + licenseState: ILicenseState +) => { + router.delete( + { + path: `${BASE_ALERTING_API_PATH}/rule/{id}`, + validate: { + params: paramSchema, + }, + }, + router.handleLegacyErrors( + verifyAccessAndContext(licenseState, async function (context, req, res) { + const alertsClient = context.alerting.getAlertsClient(); + const { id } = req.params; + await alertsClient.delete({ id }); + return res.noContent(); + }) + ) + ); +}; diff --git a/x-pack/plugins/alerting/server/routes/disable_rule.test.ts b/x-pack/plugins/alerting/server/routes/disable_rule.test.ts new file mode 100644 index 00000000000000..a77a8443a97fb1 --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/disable_rule.test.ts @@ -0,0 +1,81 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { disableRuleRoute } from './disable_rule'; +import { httpServiceMock } from 'src/core/server/mocks'; +import { licenseStateMock } from '../lib/license_state.mock'; +import { mockHandlerArguments } from './_mock_handler_arguments'; +import { alertsClientMock } from '../alerts_client.mock'; +import { AlertTypeDisabledError } from '../lib/errors/alert_type_disabled'; + +const alertsClient = alertsClientMock.create(); + +jest.mock('../lib/license_api_access.ts', () => ({ + verifyApiAccess: jest.fn(), +})); + +beforeEach(() => { + jest.resetAllMocks(); +}); + +describe('disableRuleRoute', () => { + it('disables a rule', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + disableRuleRoute(router, licenseState); + + const [config, handler] = router.post.mock.calls[0]; + + expect(config.path).toMatchInlineSnapshot(`"/api/alerting/rule/{id}/_disable"`); + + alertsClient.disable.mockResolvedValueOnce(); + + const [context, req, res] = mockHandlerArguments( + { alertsClient }, + { + params: { + id: '1', + }, + }, + ['noContent'] + ); + + expect(await handler(context, req, res)).toEqual(undefined); + + expect(alertsClient.disable).toHaveBeenCalledTimes(1); + expect(alertsClient.disable.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "id": "1", + }, + ] + `); + + expect(res.noContent).toHaveBeenCalled(); + }); + + it('ensures the rule type gets validated for the license', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + disableRuleRoute(router, licenseState); + + const [, handler] = router.post.mock.calls[0]; + + alertsClient.disable.mockRejectedValue(new AlertTypeDisabledError('Fail', 'license_invalid')); + + const [context, req, res] = mockHandlerArguments({ alertsClient }, { params: {}, body: {} }, [ + 'ok', + 'forbidden', + ]); + + await handler(context, req, res); + + expect(res.forbidden).toHaveBeenCalledWith({ body: { message: 'Fail' } }); + }); +}); diff --git a/x-pack/plugins/alerting/server/routes/disable_rule.ts b/x-pack/plugins/alerting/server/routes/disable_rule.ts new file mode 100644 index 00000000000000..2a2f0f4aa25fc0 --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/disable_rule.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { IRouter } from 'kibana/server'; +import { schema } from '@kbn/config-schema'; +import { ILicenseState, AlertTypeDisabledError } from '../lib'; +import { verifyAccessAndContext } from './lib'; +import { AlertingRequestHandlerContext, BASE_ALERTING_API_PATH } from '../types'; + +const paramSchema = schema.object({ + id: schema.string(), +}); + +export const disableRuleRoute = ( + router: IRouter, + licenseState: ILicenseState +) => { + router.post( + { + path: `${BASE_ALERTING_API_PATH}/rule/{id}/_disable`, + validate: { + params: paramSchema, + }, + }, + router.handleLegacyErrors( + verifyAccessAndContext(licenseState, async function (context, req, res) { + const alertsClient = context.alerting.getAlertsClient(); + const { id } = req.params; + try { + await alertsClient.disable({ id }); + return res.noContent(); + } catch (e) { + if (e instanceof AlertTypeDisabledError) { + return e.sendResponse(res); + } + throw e; + } + }) + ) + ); +}; diff --git a/x-pack/plugins/alerting/server/routes/enable_rule.test.ts b/x-pack/plugins/alerting/server/routes/enable_rule.test.ts new file mode 100644 index 00000000000000..71889d153ce5fe --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/enable_rule.test.ts @@ -0,0 +1,81 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { enableRuleRoute } from './enable_rule'; +import { httpServiceMock } from 'src/core/server/mocks'; +import { licenseStateMock } from '../lib/license_state.mock'; +import { mockHandlerArguments } from './_mock_handler_arguments'; +import { alertsClientMock } from '../alerts_client.mock'; +import { AlertTypeDisabledError } from '../lib/errors/alert_type_disabled'; + +const alertsClient = alertsClientMock.create(); + +jest.mock('../lib/license_api_access.ts', () => ({ + verifyApiAccess: jest.fn(), +})); + +beforeEach(() => { + jest.resetAllMocks(); +}); + +describe('enableRuleRoute', () => { + it('enables a rule', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + enableRuleRoute(router, licenseState); + + const [config, handler] = router.post.mock.calls[0]; + + expect(config.path).toMatchInlineSnapshot(`"/api/alerting/rule/{id}/_enable"`); + + alertsClient.enable.mockResolvedValueOnce(); + + const [context, req, res] = mockHandlerArguments( + { alertsClient }, + { + params: { + id: '1', + }, + }, + ['noContent'] + ); + + expect(await handler(context, req, res)).toEqual(undefined); + + expect(alertsClient.enable).toHaveBeenCalledTimes(1); + expect(alertsClient.enable.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "id": "1", + }, + ] + `); + + expect(res.noContent).toHaveBeenCalled(); + }); + + it('ensures the rule type gets validated for the license', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + enableRuleRoute(router, licenseState); + + const [, handler] = router.post.mock.calls[0]; + + alertsClient.enable.mockRejectedValue(new AlertTypeDisabledError('Fail', 'license_invalid')); + + const [context, req, res] = mockHandlerArguments({ alertsClient }, { params: {}, body: {} }, [ + 'ok', + 'forbidden', + ]); + + await handler(context, req, res); + + expect(res.forbidden).toHaveBeenCalledWith({ body: { message: 'Fail' } }); + }); +}); diff --git a/x-pack/plugins/alerting/server/routes/enable_rule.ts b/x-pack/plugins/alerting/server/routes/enable_rule.ts new file mode 100644 index 00000000000000..9c7526630d0a3f --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/enable_rule.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { IRouter } from 'kibana/server'; +import { schema } from '@kbn/config-schema'; +import { ILicenseState, AlertTypeDisabledError } from '../lib'; +import { verifyAccessAndContext } from './lib'; +import { AlertingRequestHandlerContext, BASE_ALERTING_API_PATH } from '../types'; + +const paramSchema = schema.object({ + id: schema.string(), +}); + +export const enableRuleRoute = ( + router: IRouter, + licenseState: ILicenseState +) => { + router.post( + { + path: `${BASE_ALERTING_API_PATH}/rule/{id}/_enable`, + validate: { + params: paramSchema, + }, + }, + router.handleLegacyErrors( + verifyAccessAndContext(licenseState, async function (context, req, res) { + const alertsClient = context.alerting.getAlertsClient(); + const { id } = req.params; + try { + await alertsClient.enable({ id }); + return res.noContent(); + } catch (e) { + if (e instanceof AlertTypeDisabledError) { + return e.sendResponse(res); + } + throw e; + } + }) + ) + ); +}; diff --git a/x-pack/plugins/alerting/server/routes/find_rules.test.ts b/x-pack/plugins/alerting/server/routes/find_rules.test.ts new file mode 100644 index 00000000000000..98bb3c77daecc2 --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/find_rules.test.ts @@ -0,0 +1,148 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { findRulesRoute } from './find_rules'; +import { httpServiceMock } from 'src/core/server/mocks'; +import { licenseStateMock } from '../lib/license_state.mock'; +import { verifyApiAccess } from '../lib/license_api_access'; +import { mockHandlerArguments } from './_mock_handler_arguments'; +import { alertsClientMock } from '../alerts_client.mock'; + +const alertsClient = alertsClientMock.create(); + +jest.mock('../lib/license_api_access.ts', () => ({ + verifyApiAccess: jest.fn(), +})); + +beforeEach(() => { + jest.resetAllMocks(); +}); + +describe('findRulesRoute', () => { + it('finds rules with proper parameters', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + findRulesRoute(router, licenseState); + + const [config, handler] = router.get.mock.calls[0]; + + expect(config.path).toMatchInlineSnapshot(`"/api/alerting/rules/_find"`); + + const findResult = { + page: 1, + perPage: 1, + total: 0, + data: [], + }; + alertsClient.find.mockResolvedValueOnce(findResult); + + const [context, req, res] = mockHandlerArguments( + { alertsClient }, + { + query: { + per_page: 1, + page: 1, + default_search_operator: 'OR', + }, + }, + ['ok'] + ); + + expect(await handler(context, req, res)).toMatchInlineSnapshot(` + Object { + "body": Object { + "data": Array [], + "page": 1, + "per_page": 1, + "total": 0, + }, + } + `); + + expect(alertsClient.find).toHaveBeenCalledTimes(1); + expect(alertsClient.find.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "options": Object { + "defaultSearchOperator": "OR", + "page": 1, + "perPage": 1, + }, + }, + ] + `); + + expect(res.ok).toHaveBeenCalledWith({ + body: { + page: 1, + per_page: 1, + total: 0, + data: [], + }, + }); + }); + + it('ensures the license allows finding rules', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + findRulesRoute(router, licenseState); + + const [, handler] = router.get.mock.calls[0]; + + alertsClient.find.mockResolvedValueOnce({ + page: 1, + perPage: 1, + total: 0, + data: [], + }); + + const [context, req, res] = mockHandlerArguments( + { alertsClient }, + { + query: { + per_page: 1, + page: 1, + default_search_operator: 'OR', + }, + } + ); + + await handler(context, req, res); + + expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); + }); + + it('ensures the license check prevents finding rules', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + (verifyApiAccess as jest.Mock).mockImplementation(() => { + throw new Error('OMG'); + }); + + findRulesRoute(router, licenseState); + + const [, handler] = router.get.mock.calls[0]; + + const [context, req, res] = mockHandlerArguments( + {}, + { + query: { + per_page: 1, + page: 1, + default_search_operator: 'OR', + }, + }, + ['ok'] + ); + expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); + + expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); + }); +}); diff --git a/x-pack/plugins/alerting/server/routes/find_rules.ts b/x-pack/plugins/alerting/server/routes/find_rules.ts new file mode 100644 index 00000000000000..06b7960b67297f --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/find_rules.ts @@ -0,0 +1,143 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { omit } from 'lodash'; +import { IRouter } from 'kibana/server'; +import { schema } from '@kbn/config-schema'; +import { ILicenseState } from '../lib'; +import { FindOptions, FindResult } from '../alerts_client'; +import { RewriteRequestCase, RewriteResponseCase, verifyAccessAndContext } from './lib'; +import { AlertTypeParams, AlertingRequestHandlerContext, BASE_ALERTING_API_PATH } from '../types'; + +// query definition +const querySchema = schema.object({ + per_page: schema.number({ defaultValue: 10, min: 0 }), + page: schema.number({ defaultValue: 1, min: 1 }), + search: schema.maybe(schema.string()), + default_search_operator: schema.oneOf([schema.literal('OR'), schema.literal('AND')], { + defaultValue: 'OR', + }), + search_fields: schema.maybe(schema.oneOf([schema.arrayOf(schema.string()), schema.string()])), + sort_field: schema.maybe(schema.string()), + sort_order: schema.maybe(schema.oneOf([schema.literal('asc'), schema.literal('desc')])), + has_reference: schema.maybe( + // use nullable as maybe is currently broken + // in config-schema + schema.nullable( + schema.object({ + type: schema.string(), + id: schema.string(), + }) + ) + ), + fields: schema.maybe(schema.arrayOf(schema.string())), + filter: schema.maybe(schema.string()), +}); + +const rewriteQueryReq: RewriteRequestCase = ({ + default_search_operator: defaultSearchOperator, + has_reference: hasReference, + search_fields: searchFields, + per_page: perPage, + sort_field: sortField, + sort_order: sortOrder, + ...rest +}) => ({ + ...rest, + defaultSearchOperator, + perPage, + ...(sortField ? { sortField } : {}), + ...(sortOrder ? { sortOrder } : {}), + ...(hasReference ? { hasReference } : {}), + ...(searchFields ? { searchFields } : {}), +}); +const rewriteBodyRes: RewriteResponseCase> = ({ + perPage, + data, + ...restOfResult +}) => { + return { + ...restOfResult, + per_page: perPage, + data: data.map( + ({ + alertTypeId, + createdBy, + updatedBy, + createdAt, + updatedAt, + apiKeyOwner, + notifyWhen, + muteAll, + mutedInstanceIds, + executionStatus, + actions, + scheduledTaskId, + ...rest + }) => ({ + ...rest, + rule_type_id: alertTypeId, + created_by: createdBy, + updated_by: updatedBy, + created_at: createdAt, + updated_at: updatedAt, + api_key_owner: apiKeyOwner, + notify_when: notifyWhen, + mute_all: muteAll, + muted_alert_ids: mutedInstanceIds, + scheduled_task_id: scheduledTaskId, + execution_status: executionStatus && { + ...omit(executionStatus, 'lastExecutionDate'), + last_execution_date: executionStatus.lastExecutionDate, + }, + actions: actions.map(({ group, id, actionTypeId, params }) => ({ + group, + id, + params, + connector_type_id: actionTypeId, + })), + }) + ), + }; +}; + +export const findRulesRoute = ( + router: IRouter, + licenseState: ILicenseState +) => { + router.get( + { + path: `${BASE_ALERTING_API_PATH}/rules/_find`, + validate: { + query: querySchema, + }, + }, + router.handleLegacyErrors( + verifyAccessAndContext(licenseState, async function (context, req, res) { + const alertsClient = context.alerting.getAlertsClient(); + + const options = rewriteQueryReq({ + ...req.query, + has_reference: req.query.has_reference || undefined, + search_fields: searchFieldsAsArray(req.query.search_fields), + }); + + const findResult = await alertsClient.find({ options }); + return res.ok({ + body: rewriteBodyRes(findResult), + }); + }) + ) + ); +}; + +function searchFieldsAsArray(searchFields: string | string[] | undefined): string[] | undefined { + if (!searchFields) { + return; + } + return Array.isArray(searchFields) ? searchFields : [searchFields]; +} diff --git a/x-pack/plugins/alerting/server/routes/get_rule.test.ts b/x-pack/plugins/alerting/server/routes/get_rule.test.ts new file mode 100644 index 00000000000000..fc900797cdc89b --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/get_rule.test.ts @@ -0,0 +1,169 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { pick } from 'lodash'; +import { getRuleRoute } from './get_rule'; +import { httpServiceMock } from 'src/core/server/mocks'; +import { licenseStateMock } from '../lib/license_state.mock'; +import { verifyApiAccess } from '../lib/license_api_access'; +import { mockHandlerArguments } from './_mock_handler_arguments'; +import { alertsClientMock } from '../alerts_client.mock'; +import { SanitizedAlert } from '../types'; +import { AsApiContract } from './lib'; + +const alertsClient = alertsClientMock.create(); +jest.mock('../lib/license_api_access.ts', () => ({ + verifyApiAccess: jest.fn(), +})); + +beforeEach(() => { + jest.resetAllMocks(); +}); + +describe('getRuleRoute', () => { + const mockedAlert: SanitizedAlert<{ + bar: boolean; + }> = { + id: '1', + alertTypeId: '1', + schedule: { interval: '10s' }, + params: { + bar: true, + }, + createdAt: new Date(), + updatedAt: new Date(), + actions: [ + { + group: 'default', + id: '2', + actionTypeId: 'test', + params: { + foo: true, + }, + }, + ], + consumer: 'bar', + name: 'abc', + tags: ['foo'], + enabled: true, + muteAll: false, + notifyWhen: 'onActionGroupChange', + createdBy: '', + updatedBy: '', + apiKeyOwner: '', + throttle: '30s', + mutedInstanceIds: [], + executionStatus: { + status: 'unknown', + lastExecutionDate: new Date('2020-08-20T19:23:38Z'), + }, + }; + + const getResult: AsApiContract> = { + ...pick(mockedAlert, 'consumer', 'name', 'schedule', 'tags', 'params', 'throttle', 'enabled'), + rule_type_id: mockedAlert.alertTypeId, + notify_when: mockedAlert.notifyWhen, + mute_all: mockedAlert.muteAll, + created_by: mockedAlert.createdBy, + updated_by: mockedAlert.updatedBy, + api_key_owner: mockedAlert.apiKeyOwner, + muted_alert_ids: mockedAlert.mutedInstanceIds, + created_at: mockedAlert.createdAt, + updated_at: mockedAlert.updatedAt, + id: mockedAlert.id, + execution_status: { + status: mockedAlert.executionStatus.status, + last_execution_date: mockedAlert.executionStatus.lastExecutionDate, + }, + actions: [ + { + group: mockedAlert.actions[0].group, + id: mockedAlert.actions[0].id, + params: mockedAlert.actions[0].params, + connector_type_id: mockedAlert.actions[0].actionTypeId, + }, + ], + }; + + it('gets a rule with proper parameters', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + getRuleRoute(router, licenseState); + const [config, handler] = router.get.mock.calls[0]; + + expect(config.path).toMatchInlineSnapshot(`"/api/alerting/rule/{id}"`); + + alertsClient.get.mockResolvedValueOnce(mockedAlert); + + const [context, req, res] = mockHandlerArguments( + { alertsClient }, + { + params: { id: '1' }, + }, + ['ok'] + ); + await handler(context, req, res); + + expect(alertsClient.get).toHaveBeenCalledTimes(1); + expect(alertsClient.get.mock.calls[0][0].id).toEqual('1'); + + expect(res.ok).toHaveBeenCalledWith({ + body: getResult, + }); + }); + + it('ensures the license allows getting rules', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + getRuleRoute(router, licenseState); + + const [, handler] = router.get.mock.calls[0]; + + alertsClient.get.mockResolvedValueOnce(mockedAlert); + + const [context, req, res] = mockHandlerArguments( + { alertsClient }, + { + params: { id: '1' }, + }, + ['ok'] + ); + + await handler(context, req, res); + + expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); + }); + + it('ensures the license check prevents getting rules', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + (verifyApiAccess as jest.Mock).mockImplementation(() => { + throw new Error('OMG'); + }); + + getRuleRoute(router, licenseState); + + const [, handler] = router.get.mock.calls[0]; + + alertsClient.get.mockResolvedValueOnce(mockedAlert); + + const [context, req, res] = mockHandlerArguments( + { alertsClient }, + { + params: { id: '1' }, + }, + ['ok'] + ); + + expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); + + expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); + }); +}); diff --git a/x-pack/plugins/alerting/server/routes/get_rule.ts b/x-pack/plugins/alerting/server/routes/get_rule.ts new file mode 100644 index 00000000000000..fd03f32047e749 --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/get_rule.ts @@ -0,0 +1,84 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { omit } from 'lodash'; +import { schema } from '@kbn/config-schema'; +import { IRouter } from 'kibana/server'; +import { ILicenseState } from '../lib'; +import { verifyAccessAndContext, RewriteResponseCase } from './lib'; +import { + AlertTypeParams, + AlertingRequestHandlerContext, + BASE_ALERTING_API_PATH, + SanitizedAlert, +} from '../types'; + +const paramSchema = schema.object({ + id: schema.string(), +}); + +const rewriteBodyRes: RewriteResponseCase> = ({ + alertTypeId, + createdBy, + updatedBy, + createdAt, + updatedAt, + apiKeyOwner, + notifyWhen, + muteAll, + mutedInstanceIds, + executionStatus, + actions, + scheduledTaskId, + ...rest +}) => ({ + ...rest, + rule_type_id: alertTypeId, + created_by: createdBy, + updated_by: updatedBy, + created_at: createdAt, + updated_at: updatedAt, + api_key_owner: apiKeyOwner, + notify_when: notifyWhen, + mute_all: muteAll, + muted_alert_ids: mutedInstanceIds, + scheduled_task_id: scheduledTaskId, + execution_status: executionStatus && { + ...omit(executionStatus, 'lastExecutionDate'), + last_execution_date: executionStatus.lastExecutionDate, + }, + actions: actions.map(({ group, id, actionTypeId, params }) => ({ + group, + id, + params, + connector_type_id: actionTypeId, + })), +}); + +export const getRuleRoute = ( + router: IRouter, + licenseState: ILicenseState +) => { + router.get( + { + path: `${BASE_ALERTING_API_PATH}/rule/{id}`, + validate: { + params: paramSchema, + }, + }, + router.handleLegacyErrors( + verifyAccessAndContext(licenseState, async function (context, req, res) { + const alertsClient = context.alerting.getAlertsClient(); + const { id } = req.params; + const rule = await alertsClient.get({ id }); + return res.ok({ + body: rewriteBodyRes(rule), + }); + }) + ) + ); +}; diff --git a/x-pack/plugins/alerting/server/routes/get_rule_alert_summary.test.ts b/x-pack/plugins/alerting/server/routes/get_rule_alert_summary.test.ts new file mode 100644 index 00000000000000..fab6d46219a373 --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/get_rule_alert_summary.test.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { getRuleAlertSummaryRoute } from './get_rule_alert_summary'; +import { httpServiceMock } from 'src/core/server/mocks'; +import { licenseStateMock } from '../lib/license_state.mock'; +import { mockHandlerArguments } from './_mock_handler_arguments'; +import { SavedObjectsErrorHelpers } from 'src/core/server'; +import { alertsClientMock } from '../alerts_client.mock'; +import { AlertInstanceSummary } from '../types'; + +const alertsClient = alertsClientMock.create(); +jest.mock('../lib/license_api_access.ts', () => ({ + verifyApiAccess: jest.fn(), +})); + +beforeEach(() => { + jest.resetAllMocks(); +}); + +describe('getRuleAlertSummaryRoute', () => { + const dateString = new Date().toISOString(); + const mockedAlertInstanceSummary: AlertInstanceSummary = { + id: '', + name: '', + tags: [], + alertTypeId: '', + consumer: '', + muteAll: false, + throttle: null, + enabled: false, + statusStartDate: dateString, + statusEndDate: dateString, + status: 'OK', + errorMessages: [], + instances: {}, + }; + + it('gets rule alert summary', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + getRuleAlertSummaryRoute(router, licenseState); + + const [config, handler] = router.get.mock.calls[0]; + + expect(config.path).toMatchInlineSnapshot(`"/internal/alerting/rule/{id}/_alert_summary"`); + + alertsClient.getAlertInstanceSummary.mockResolvedValueOnce(mockedAlertInstanceSummary); + + const [context, req, res] = mockHandlerArguments( + { alertsClient }, + { + params: { + id: '1', + }, + query: {}, + }, + ['ok'] + ); + + await handler(context, req, res); + + expect(alertsClient.getAlertInstanceSummary).toHaveBeenCalledTimes(1); + expect(alertsClient.getAlertInstanceSummary.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "dateStart": undefined, + "id": "1", + }, + ] + `); + + expect(res.ok).toHaveBeenCalled(); + }); + + it('returns NOT-FOUND when rule is not found', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + getRuleAlertSummaryRoute(router, licenseState); + + const [, handler] = router.get.mock.calls[0]; + + alertsClient.getAlertInstanceSummary = jest + .fn() + .mockResolvedValueOnce(SavedObjectsErrorHelpers.createGenericNotFoundError('alert', '1')); + + const [context, req, res] = mockHandlerArguments( + { alertsClient }, + { + params: { + id: '1', + }, + query: {}, + }, + ['notFound'] + ); + + expect(await handler(context, req, res)).toEqual(undefined); + }); +}); diff --git a/x-pack/plugins/alerting/server/routes/get_rule_alert_summary.ts b/x-pack/plugins/alerting/server/routes/get_rule_alert_summary.ts new file mode 100644 index 00000000000000..7a3679851d53ff --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/get_rule_alert_summary.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { IRouter } from 'kibana/server'; +import { schema } from '@kbn/config-schema'; +import { ILicenseState } from '../lib'; +import { GetAlertInstanceSummaryParams } from '../alerts_client'; +import { RewriteRequestCase, RewriteResponseCase, verifyAccessAndContext } from './lib'; +import { + AlertingRequestHandlerContext, + INTERNAL_BASE_ALERTING_API_PATH, + AlertInstanceSummary, +} from '../types'; + +const paramSchema = schema.object({ + id: schema.string(), +}); + +const querySchema = schema.object({ + date_start: schema.maybe(schema.string()), +}); + +const rewriteReq: RewriteRequestCase = ({ + date_start: dateStart, + ...rest +}) => ({ + ...rest, + dateStart, +}); +const rewriteBodyRes: RewriteResponseCase = ({ + alertTypeId, + muteAll, + statusStartDate, + statusEndDate, + errorMessages, + lastRun, + instances: alerts, + ...rest +}) => ({ + ...rest, + alerts, + rule_type_id: alertTypeId, + mute_all: muteAll, + status_start_date: statusStartDate, + status_end_date: statusEndDate, + error_messages: errorMessages, + last_run: lastRun, +}); + +export const getRuleAlertSummaryRoute = ( + router: IRouter, + licenseState: ILicenseState +) => { + router.get( + { + path: `${INTERNAL_BASE_ALERTING_API_PATH}/rule/{id}/_alert_summary`, + validate: { + params: paramSchema, + query: querySchema, + }, + }, + router.handleLegacyErrors( + verifyAccessAndContext(licenseState, async function (context, req, res) { + const alertsClient = context.alerting.getAlertsClient(); + const { id } = req.params; + const summary = await alertsClient.getAlertInstanceSummary( + rewriteReq({ id, ...req.query }) + ); + return res.ok({ body: rewriteBodyRes(summary) }); + }) + ) + ); +}; diff --git a/x-pack/plugins/alerting/server/routes/get_rule_state.test.ts b/x-pack/plugins/alerting/server/routes/get_rule_state.test.ts new file mode 100644 index 00000000000000..71e06b60d1026f --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/get_rule_state.test.ts @@ -0,0 +1,150 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { getRuleStateRoute } from './get_rule_state'; +import { httpServiceMock } from 'src/core/server/mocks'; +import { licenseStateMock } from '../lib/license_state.mock'; +import { mockHandlerArguments } from './_mock_handler_arguments'; +import { SavedObjectsErrorHelpers } from 'src/core/server'; +import { alertsClientMock } from '../alerts_client.mock'; + +const alertsClient = alertsClientMock.create(); +jest.mock('../lib/license_api_access.ts', () => ({ + verifyApiAccess: jest.fn(), +})); + +beforeEach(() => { + jest.resetAllMocks(); +}); + +describe('getRuleStateRoute', () => { + const mockedAlertState = { + alertTypeState: { + some: 'value', + }, + alertInstances: { + first_instance: { + state: {}, + meta: { + lastScheduledActions: { + group: 'first_group', + date: new Date(), + }, + }, + }, + second_instance: {}, + }, + }; + + it('gets rule state', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + getRuleStateRoute(router, licenseState); + + const [config, handler] = router.get.mock.calls[0]; + + expect(config.path).toMatchInlineSnapshot(`"/internal/alerting/rule/{id}/state"`); + + alertsClient.getAlertState.mockResolvedValueOnce(mockedAlertState); + + const [context, req, res] = mockHandlerArguments( + { alertsClient }, + { + params: { + id: '1', + }, + }, + ['ok'] + ); + + await handler(context, req, res); + + expect(alertsClient.getAlertState).toHaveBeenCalledTimes(1); + expect(alertsClient.getAlertState.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "id": "1", + }, + ] + `); + + expect(res.ok).toHaveBeenCalled(); + }); + + it('returns NO-CONTENT when rule exists but has no task state yet', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + getRuleStateRoute(router, licenseState); + + const [config, handler] = router.get.mock.calls[0]; + + expect(config.path).toMatchInlineSnapshot(`"/internal/alerting/rule/{id}/state"`); + + alertsClient.getAlertState.mockResolvedValueOnce(undefined); + + const [context, req, res] = mockHandlerArguments( + { alertsClient }, + { + params: { + id: '1', + }, + }, + ['noContent'] + ); + + expect(await handler(context, req, res)).toEqual(undefined); + + expect(alertsClient.getAlertState).toHaveBeenCalledTimes(1); + expect(alertsClient.getAlertState.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "id": "1", + }, + ] + `); + + expect(res.noContent).toHaveBeenCalled(); + }); + + it('returns NOT-FOUND when rule is not found', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + getRuleStateRoute(router, licenseState); + + const [config, handler] = router.get.mock.calls[0]; + + expect(config.path).toMatchInlineSnapshot(`"/internal/alerting/rule/{id}/state"`); + + alertsClient.getAlertState = jest + .fn() + .mockResolvedValueOnce(SavedObjectsErrorHelpers.createGenericNotFoundError('alert', '1')); + + const [context, req, res] = mockHandlerArguments( + { alertsClient }, + { + params: { + id: '1', + }, + }, + ['notFound'] + ); + + expect(await handler(context, req, res)).toEqual(undefined); + + expect(alertsClient.getAlertState).toHaveBeenCalledTimes(1); + expect(alertsClient.getAlertState.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "id": "1", + }, + ] + `); + }); +}); diff --git a/x-pack/plugins/alerting/server/routes/get_rule_state.ts b/x-pack/plugins/alerting/server/routes/get_rule_state.ts new file mode 100644 index 00000000000000..08087d1ece8afa --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/get_rule_state.ts @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { IRouter } from 'kibana/server'; +import { schema } from '@kbn/config-schema'; +import { ILicenseState } from '../lib'; +import { RewriteResponseCase, verifyAccessAndContext } from './lib'; +import { + AlertingRequestHandlerContext, + INTERNAL_BASE_ALERTING_API_PATH, + AlertTaskState, +} from '../types'; + +const paramSchema = schema.object({ + id: schema.string(), +}); + +const rewriteBodyRes: RewriteResponseCase = ({ + alertTypeState, + alertInstances, + previousStartedAt, + ...rest +}) => ({ + ...rest, + rule_type_state: alertTypeState, + alerts: alertInstances, + previous_started_at: previousStartedAt, +}); + +export const getRuleStateRoute = ( + router: IRouter, + licenseState: ILicenseState +) => { + router.get( + { + path: `${INTERNAL_BASE_ALERTING_API_PATH}/rule/{id}/state`, + validate: { + params: paramSchema, + }, + }, + router.handleLegacyErrors( + verifyAccessAndContext(licenseState, async function (context, req, res) { + const alertsClient = context.alerting.getAlertsClient(); + const { id } = req.params; + const state = await alertsClient.getAlertState({ id }); + return state ? res.ok({ body: rewriteBodyRes(state) }) : res.noContent(); + }) + ) + ); +}; diff --git a/x-pack/plugins/alerting/server/routes/health.test.ts b/x-pack/plugins/alerting/server/routes/health.test.ts index 75c621e4a0abf3..be63e0b7054be2 100644 --- a/x-pack/plugins/alerting/server/routes/health.test.ts +++ b/x-pack/plugins/alerting/server/routes/health.test.ts @@ -54,7 +54,7 @@ describe('healthRoute', () => { const [config] = router.get.mock.calls[0]; - expect(config.path).toMatchInlineSnapshot(`"/api/alerts/_health"`); + expect(config.path).toMatchInlineSnapshot(`"/api/alerting/_health"`); }); it('queries the usage api', async () => { @@ -107,22 +107,22 @@ describe('healthRoute', () => { expect(await handler(context, req, res)).toStrictEqual({ body: { - alertingFrameworkHeath: { - decryptionHealth: { + alerting_framework_heath: { + decryption_health: { status: HealthStatus.OK, timestamp: currentDate, }, - executionHealth: { + execution_health: { status: HealthStatus.OK, timestamp: currentDate, }, - readHealth: { + read_health: { status: HealthStatus.OK, timestamp: currentDate, }, }, - hasPermanentEncryptionKey: false, - isSufficientlySecure: true, + has_permanent_encryption_key: false, + is_sufficiently_secure: true, }, }); }); @@ -148,22 +148,22 @@ describe('healthRoute', () => { expect(await handler(context, req, res)).toStrictEqual({ body: { - alertingFrameworkHeath: { - decryptionHealth: { + alerting_framework_heath: { + decryption_health: { status: HealthStatus.OK, timestamp: currentDate, }, - executionHealth: { + execution_health: { status: HealthStatus.OK, timestamp: currentDate, }, - readHealth: { + read_health: { status: HealthStatus.OK, timestamp: currentDate, }, }, - hasPermanentEncryptionKey: true, - isSufficientlySecure: true, + has_permanent_encryption_key: true, + is_sufficiently_secure: true, }, }); }); @@ -189,22 +189,22 @@ describe('healthRoute', () => { expect(await handler(context, req, res)).toStrictEqual({ body: { - alertingFrameworkHeath: { - decryptionHealth: { + alerting_framework_heath: { + decryption_health: { status: HealthStatus.OK, timestamp: currentDate, }, - executionHealth: { + execution_health: { status: HealthStatus.OK, timestamp: currentDate, }, - readHealth: { + read_health: { status: HealthStatus.OK, timestamp: currentDate, }, }, - hasPermanentEncryptionKey: true, - isSufficientlySecure: true, + has_permanent_encryption_key: true, + is_sufficiently_secure: true, }, }); }); @@ -230,22 +230,22 @@ describe('healthRoute', () => { expect(await handler(context, req, res)).toStrictEqual({ body: { - alertingFrameworkHeath: { - decryptionHealth: { + alerting_framework_heath: { + decryption_health: { status: HealthStatus.OK, timestamp: currentDate, }, - executionHealth: { + execution_health: { status: HealthStatus.OK, timestamp: currentDate, }, - readHealth: { + read_health: { status: HealthStatus.OK, timestamp: currentDate, }, }, - hasPermanentEncryptionKey: true, - isSufficientlySecure: false, + has_permanent_encryption_key: true, + is_sufficiently_secure: false, }, }); }); @@ -273,22 +273,22 @@ describe('healthRoute', () => { expect(await handler(context, req, res)).toStrictEqual({ body: { - alertingFrameworkHeath: { - decryptionHealth: { + alerting_framework_heath: { + decryption_health: { status: HealthStatus.OK, timestamp: currentDate, }, - executionHealth: { + execution_health: { status: HealthStatus.OK, timestamp: currentDate, }, - readHealth: { + read_health: { status: HealthStatus.OK, timestamp: currentDate, }, }, - hasPermanentEncryptionKey: true, - isSufficientlySecure: false, + has_permanent_encryption_key: true, + is_sufficiently_secure: false, }, }); }); @@ -316,22 +316,22 @@ describe('healthRoute', () => { expect(await handler(context, req, res)).toStrictEqual({ body: { - alertingFrameworkHeath: { - decryptionHealth: { + alerting_framework_heath: { + decryption_health: { status: HealthStatus.OK, timestamp: currentDate, }, - executionHealth: { + execution_health: { status: HealthStatus.OK, timestamp: currentDate, }, - readHealth: { + read_health: { status: HealthStatus.OK, timestamp: currentDate, }, }, - hasPermanentEncryptionKey: true, - isSufficientlySecure: true, + has_permanent_encryption_key: true, + is_sufficiently_secure: true, }, }); }); diff --git a/x-pack/plugins/alerting/server/routes/health.ts b/x-pack/plugins/alerting/server/routes/health.ts index de0b14465c5acf..c2a122a28fa490 100644 --- a/x-pack/plugins/alerting/server/routes/health.ts +++ b/x-pack/plugins/alerting/server/routes/health.ts @@ -6,11 +6,15 @@ */ import { ApiResponse } from '@elastic/elasticsearch'; -import type { AlertingRouter } from '../types'; -import { ILicenseState } from '../lib/license_state'; -import { verifyApiAccess } from '../lib/license_api_access'; -import { AlertingFrameworkHealth } from '../types'; +import { IRouter } from 'kibana/server'; +import { ILicenseState } from '../lib'; import { EncryptedSavedObjectsPluginSetup } from '../../../encrypted_saved_objects/server'; +import { RewriteResponseCase, verifyAccessAndContext } from './lib'; +import { + AlertingRequestHandlerContext, + BASE_ALERTING_API_PATH, + AlertingFrameworkHealth, +} from '../types'; interface XPackUsageSecurity { security?: { @@ -23,49 +27,63 @@ interface XPackUsageSecurity { }; } -export function healthRoute( - router: AlertingRouter, +const rewriteBodyRes: RewriteResponseCase = ({ + isSufficientlySecure, + hasPermanentEncryptionKey, + alertingFrameworkHeath, + ...rest +}) => ({ + ...rest, + is_sufficiently_secure: isSufficientlySecure, + has_permanent_encryption_key: hasPermanentEncryptionKey, + alerting_framework_heath: { + decryption_health: alertingFrameworkHeath.decryptionHealth, + execution_health: alertingFrameworkHeath.executionHealth, + read_health: alertingFrameworkHeath.readHealth, + }, +}); + +export const healthRoute = ( + router: IRouter, licenseState: ILicenseState, encryptedSavedObjects: EncryptedSavedObjectsPluginSetup -) { +) => { router.get( { - path: '/api/alerts/_health', + path: `${BASE_ALERTING_API_PATH}/_health`, validate: false, }, - router.handleLegacyErrors(async function (context, req, res) { - verifyApiAccess(licenseState); - if (!context.alerting) { - return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' }); - } - try { - const { - body: { - security: { - enabled: isSecurityEnabled = false, - ssl: { http: { enabled: isTLSEnabled = false } = {} } = {}, - } = {}, - }, - }: ApiResponse = await context.core.elasticsearch.client.asInternalUser.transport // Do not augment with such input. // `transport.request` is potentially unsafe when combined with untrusted user input. - .request({ - method: 'GET', - path: '/_xpack/usage', - }); + router.handleLegacyErrors( + verifyAccessAndContext(licenseState, async function (context, req, res) { + try { + const { + body: { + security: { + enabled: isSecurityEnabled = false, + ssl: { http: { enabled: isTLSEnabled = false } = {} } = {}, + } = {}, + }, + }: ApiResponse = await context.core.elasticsearch.client.asInternalUser.transport // Do not augment with such input. // `transport.request` is potentially unsafe when combined with untrusted user input. + .request({ + method: 'GET', + path: '/_xpack/usage', + }); - const alertingFrameworkHeath = await context.alerting.getFrameworkHealth(); + const alertingFrameworkHeath = await context.alerting.getFrameworkHealth(); - const frameworkHealth: AlertingFrameworkHealth = { - isSufficientlySecure: !isSecurityEnabled || (isSecurityEnabled && isTLSEnabled), - hasPermanentEncryptionKey: encryptedSavedObjects.canEncrypt, - alertingFrameworkHeath, - }; + const frameworkHealth: AlertingFrameworkHealth = { + isSufficientlySecure: !isSecurityEnabled || (isSecurityEnabled && isTLSEnabled), + hasPermanentEncryptionKey: encryptedSavedObjects.canEncrypt, + alertingFrameworkHeath, + }; - return res.ok({ - body: frameworkHealth, - }); - } catch (error) { - return res.badRequest({ body: error }); - } - }) + return res.ok({ + body: rewriteBodyRes(frameworkHealth), + }); + } catch (error) { + return res.badRequest({ body: error }); + } + }) + ) ); -} +}; diff --git a/x-pack/plugins/alerting/server/routes/index.ts b/x-pack/plugins/alerting/server/routes/index.ts index e250d17866a2b1..c6f12ffba2f20c 100644 --- a/x-pack/plugins/alerting/server/routes/index.ts +++ b/x-pack/plugins/alerting/server/routes/index.ts @@ -5,20 +5,50 @@ * 2.0. */ -export { aggregateAlertRoute } from './aggregate'; -export { createAlertRoute } from './create'; -export { deleteAlertRoute } from './delete'; -export { findAlertRoute } from './find'; -export { getAlertRoute } from './get'; -export { getAlertStateRoute } from './get_alert_state'; -export { getAlertInstanceSummaryRoute } from './get_alert_instance_summary'; -export { listAlertTypesRoute } from './list_alert_types'; -export { updateAlertRoute } from './update'; -export { enableAlertRoute } from './enable'; -export { disableAlertRoute } from './disable'; -export { updateApiKeyRoute } from './update_api_key'; -export { muteAlertInstanceRoute } from './mute_instance'; -export { unmuteAlertInstanceRoute } from './unmute_instance'; -export { muteAllAlertRoute } from './mute_all'; -export { unmuteAllAlertRoute } from './unmute_all'; -export { healthRoute } from './health'; +import { IRouter } from 'kibana/server'; +import { ILicenseState } from '../lib'; +import { defineLegacyRoutes } from './legacy'; +import { AlertingRequestHandlerContext } from '../types'; +import { EncryptedSavedObjectsPluginSetup } from '../../../encrypted_saved_objects/server'; +import { createRuleRoute } from './create_rule'; +import { getRuleRoute } from './get_rule'; +import { updateRuleRoute } from './update_rule'; +import { deleteRuleRoute } from './delete_rule'; +import { aggregateRulesRoute } from './aggregate_rules'; +import { disableRuleRoute } from './disable_rule'; +import { enableRuleRoute } from './enable_rule'; +import { findRulesRoute } from './find_rules'; +import { getRuleAlertSummaryRoute } from './get_rule_alert_summary'; +import { getRuleStateRoute } from './get_rule_state'; +import { healthRoute } from './health'; +import { ruleTypesRoute } from './rule_types'; +import { muteAllRuleRoute } from './mute_all_rule'; +import { muteAlertRoute } from './mute_alert'; +import { unmuteAllRuleRoute } from './unmute_all_rule'; +import { unmuteAlertRoute } from './unmute_alert'; +import { updateRuleApiKeyRoute } from './update_rule_api_key'; + +export function defineRoutes( + router: IRouter, + licenseState: ILicenseState, + encryptedSavedObjects: EncryptedSavedObjectsPluginSetup +) { + defineLegacyRoutes(router, licenseState, encryptedSavedObjects); + createRuleRoute(router, licenseState); + getRuleRoute(router, licenseState); + updateRuleRoute(router, licenseState); + deleteRuleRoute(router, licenseState); + aggregateRulesRoute(router, licenseState); + disableRuleRoute(router, licenseState); + enableRuleRoute(router, licenseState); + findRulesRoute(router, licenseState); + getRuleAlertSummaryRoute(router, licenseState); + getRuleStateRoute(router, licenseState); + healthRoute(router, licenseState, encryptedSavedObjects); + ruleTypesRoute(router, licenseState); + muteAllRuleRoute(router, licenseState); + muteAlertRoute(router, licenseState); + unmuteAllRuleRoute(router, licenseState); + unmuteAlertRoute(router, licenseState); + updateRuleApiKeyRoute(router, licenseState); +} diff --git a/x-pack/plugins/alerting/server/routes/aggregate.test.ts b/x-pack/plugins/alerting/server/routes/legacy/aggregate.test.ts similarity index 91% rename from x-pack/plugins/alerting/server/routes/aggregate.test.ts rename to x-pack/plugins/alerting/server/routes/legacy/aggregate.test.ts index 60e9a2a1bcc79c..94331902d9aceb 100644 --- a/x-pack/plugins/alerting/server/routes/aggregate.test.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/aggregate.test.ts @@ -7,14 +7,14 @@ import { aggregateAlertRoute } from './aggregate'; import { httpServiceMock } from 'src/core/server/mocks'; -import { licenseStateMock } from '../lib/license_state.mock'; -import { verifyApiAccess } from '../lib/license_api_access'; -import { mockHandlerArguments } from './_mock_handler_arguments'; -import { alertsClientMock } from '../alerts_client.mock'; +import { licenseStateMock } from '../../lib/license_state.mock'; +import { verifyApiAccess } from '../../lib/license_api_access'; +import { mockHandlerArguments } from './../_mock_handler_arguments'; +import { alertsClientMock } from '../../alerts_client.mock'; const alertsClient = alertsClientMock.create(); -jest.mock('../lib/license_api_access.ts', () => ({ +jest.mock('../../lib/license_api_access.ts', () => ({ verifyApiAccess: jest.fn(), })); diff --git a/x-pack/plugins/alerting/server/routes/aggregate.ts b/x-pack/plugins/alerting/server/routes/legacy/aggregate.ts similarity index 83% rename from x-pack/plugins/alerting/server/routes/aggregate.ts rename to x-pack/plugins/alerting/server/routes/legacy/aggregate.ts index 1416f60277daa0..91189fdf3d0a6c 100644 --- a/x-pack/plugins/alerting/server/routes/aggregate.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/aggregate.ts @@ -6,12 +6,12 @@ */ import { schema } from '@kbn/config-schema'; -import type { AlertingRouter } from '../types'; -import { ILicenseState } from '../lib/license_state'; -import { verifyApiAccess } from '../lib/license_api_access'; -import { BASE_ALERT_API_PATH } from '../../common'; -import { renameKeys } from './lib/rename_keys'; -import { FindOptions } from '../alerts_client'; +import type { AlertingRouter } from '../../types'; +import { ILicenseState } from '../../lib/license_state'; +import { verifyApiAccess } from '../../lib/license_api_access'; +import { LEGACY_BASE_ALERT_API_PATH } from '../../../common'; +import { renameKeys } from './../lib/rename_keys'; +import { FindOptions } from '../../alerts_client'; // config definition const querySchema = schema.object({ @@ -36,7 +36,7 @@ const querySchema = schema.object({ export const aggregateAlertRoute = (router: AlertingRouter, licenseState: ILicenseState) => { router.get( { - path: `${BASE_ALERT_API_PATH}/_aggregate`, + path: `${LEGACY_BASE_ALERT_API_PATH}/_aggregate`, validate: { query: querySchema, }, diff --git a/x-pack/plugins/alerting/server/routes/create.test.ts b/x-pack/plugins/alerting/server/routes/legacy/create.test.ts similarity index 93% rename from x-pack/plugins/alerting/server/routes/create.test.ts rename to x-pack/plugins/alerting/server/routes/legacy/create.test.ts index 1a2dfec8612e37..fd3252d2fca774 100644 --- a/x-pack/plugins/alerting/server/routes/create.test.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/create.test.ts @@ -7,16 +7,16 @@ import { createAlertRoute } from './create'; import { httpServiceMock } from 'src/core/server/mocks'; -import { licenseStateMock } from '../lib/license_state.mock'; -import { verifyApiAccess } from '../lib/license_api_access'; -import { mockHandlerArguments } from './_mock_handler_arguments'; -import { alertsClientMock } from '../alerts_client.mock'; -import { Alert } from '../../common/alert'; -import { AlertTypeDisabledError } from '../lib/errors/alert_type_disabled'; +import { licenseStateMock } from '../../lib/license_state.mock'; +import { verifyApiAccess } from '../../lib/license_api_access'; +import { mockHandlerArguments } from './../_mock_handler_arguments'; +import { alertsClientMock } from '../../alerts_client.mock'; +import { Alert } from '../../../common/alert'; +import { AlertTypeDisabledError } from '../../lib/errors/alert_type_disabled'; const alertsClient = alertsClientMock.create(); -jest.mock('../lib/license_api_access.ts', () => ({ +jest.mock('../../lib/license_api_access.ts', () => ({ verifyApiAccess: jest.fn(), })); diff --git a/x-pack/plugins/alerting/server/routes/create.ts b/x-pack/plugins/alerting/server/routes/legacy/create.ts similarity index 75% rename from x-pack/plugins/alerting/server/routes/create.ts rename to x-pack/plugins/alerting/server/routes/legacy/create.ts index 7b1a518112ddd2..fca2b671185273 100644 --- a/x-pack/plugins/alerting/server/routes/create.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/create.ts @@ -6,19 +6,19 @@ */ import { schema } from '@kbn/config-schema'; -import type { AlertingRouter } from '../types'; -import { ILicenseState } from '../lib/license_state'; -import { verifyApiAccess } from '../lib/license_api_access'; -import { validateDurationSchema } from '../lib'; -import { handleDisabledApiKeysError } from './lib/error_handler'; +import type { AlertingRouter } from '../../types'; +import { ILicenseState } from '../../lib/license_state'; +import { verifyApiAccess } from '../../lib/license_api_access'; +import { validateDurationSchema } from '../../lib'; +import { handleDisabledApiKeysError } from './../lib/error_handler'; import { - Alert, + SanitizedAlert, AlertNotifyWhenType, AlertTypeParams, - BASE_ALERT_API_PATH, + LEGACY_BASE_ALERT_API_PATH, validateNotifyWhenType, -} from '../types'; -import { AlertTypeDisabledError } from '../lib/errors/alert_type_disabled'; +} from '../../types'; +import { AlertTypeDisabledError } from '../../lib/errors/alert_type_disabled'; export const bodySchema = schema.object({ name: schema.string(), @@ -46,7 +46,7 @@ export const bodySchema = schema.object({ export const createAlertRoute = (router: AlertingRouter, licenseState: ILicenseState) => { router.post( { - path: `${BASE_ALERT_API_PATH}/alert/{id?}`, + path: `${LEGACY_BASE_ALERT_API_PATH}/alert/{id?}`, validate: { params: schema.maybe( schema.object({ @@ -68,10 +68,12 @@ export const createAlertRoute = (router: AlertingRouter, licenseState: ILicenseS const params = req.params; const notifyWhen = alert?.notifyWhen ? (alert.notifyWhen as AlertNotifyWhenType) : null; try { - const alertRes: Alert = await alertsClient.create({ - data: { ...alert, notifyWhen }, - options: { id: params?.id }, - }); + const alertRes: SanitizedAlert = await alertsClient.create( + { + data: { ...alert, notifyWhen }, + options: { id: params?.id }, + } + ); return res.ok({ body: alertRes, }); diff --git a/x-pack/plugins/alerting/server/routes/delete.test.ts b/x-pack/plugins/alerting/server/routes/legacy/delete.test.ts similarity index 89% rename from x-pack/plugins/alerting/server/routes/delete.test.ts rename to x-pack/plugins/alerting/server/routes/legacy/delete.test.ts index 47564f67d42a5a..e71b2788b98c7c 100644 --- a/x-pack/plugins/alerting/server/routes/delete.test.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/delete.test.ts @@ -7,14 +7,14 @@ import { deleteAlertRoute } from './delete'; import { httpServiceMock } from 'src/core/server/mocks'; -import { licenseStateMock } from '../lib/license_state.mock'; -import { verifyApiAccess } from '../lib/license_api_access'; -import { mockHandlerArguments } from './_mock_handler_arguments'; -import { alertsClientMock } from '../alerts_client.mock'; +import { licenseStateMock } from '../../lib/license_state.mock'; +import { verifyApiAccess } from '../../lib/license_api_access'; +import { mockHandlerArguments } from './../_mock_handler_arguments'; +import { alertsClientMock } from '../../alerts_client.mock'; const alertsClient = alertsClientMock.create(); -jest.mock('../lib/license_api_access.ts', () => ({ +jest.mock('../../lib/license_api_access.ts', () => ({ verifyApiAccess: jest.fn(), })); diff --git a/x-pack/plugins/alerting/server/routes/delete.ts b/x-pack/plugins/alerting/server/routes/legacy/delete.ts similarity index 76% rename from x-pack/plugins/alerting/server/routes/delete.ts rename to x-pack/plugins/alerting/server/routes/legacy/delete.ts index e217fd0533771a..650126be4499d5 100644 --- a/x-pack/plugins/alerting/server/routes/delete.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/delete.ts @@ -6,10 +6,10 @@ */ import { schema } from '@kbn/config-schema'; -import type { AlertingRouter } from '../types'; -import { ILicenseState } from '../lib/license_state'; -import { verifyApiAccess } from '../lib/license_api_access'; -import { BASE_ALERT_API_PATH } from '../../common'; +import type { AlertingRouter } from '../../types'; +import { ILicenseState } from '../../lib/license_state'; +import { verifyApiAccess } from '../../lib/license_api_access'; +import { LEGACY_BASE_ALERT_API_PATH } from '../../../common'; const paramSchema = schema.object({ id: schema.string(), @@ -18,7 +18,7 @@ const paramSchema = schema.object({ export const deleteAlertRoute = (router: AlertingRouter, licenseState: ILicenseState) => { router.delete( { - path: `${BASE_ALERT_API_PATH}/alert/{id}`, + path: `${LEGACY_BASE_ALERT_API_PATH}/alert/{id}`, validate: { params: paramSchema, }, diff --git a/x-pack/plugins/alerting/server/routes/disable.test.ts b/x-pack/plugins/alerting/server/routes/legacy/disable.test.ts similarity index 86% rename from x-pack/plugins/alerting/server/routes/disable.test.ts rename to x-pack/plugins/alerting/server/routes/legacy/disable.test.ts index 347b1641515e62..34c0af711a338b 100644 --- a/x-pack/plugins/alerting/server/routes/disable.test.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/disable.test.ts @@ -7,14 +7,14 @@ import { disableAlertRoute } from './disable'; import { httpServiceMock } from 'src/core/server/mocks'; -import { licenseStateMock } from '../lib/license_state.mock'; -import { mockHandlerArguments } from './_mock_handler_arguments'; -import { alertsClientMock } from '../alerts_client.mock'; -import { AlertTypeDisabledError } from '../lib/errors/alert_type_disabled'; +import { licenseStateMock } from '../../lib/license_state.mock'; +import { mockHandlerArguments } from './../_mock_handler_arguments'; +import { alertsClientMock } from '../../alerts_client.mock'; +import { AlertTypeDisabledError } from '../../lib/errors/alert_type_disabled'; const alertsClient = alertsClientMock.create(); -jest.mock('../lib/license_api_access.ts', () => ({ +jest.mock('../../lib/license_api_access.ts', () => ({ verifyApiAccess: jest.fn(), })); diff --git a/x-pack/plugins/alerting/server/routes/disable.ts b/x-pack/plugins/alerting/server/routes/legacy/disable.ts similarity index 74% rename from x-pack/plugins/alerting/server/routes/disable.ts rename to x-pack/plugins/alerting/server/routes/legacy/disable.ts index 7129fbdd52698a..140e0492fbaaba 100644 --- a/x-pack/plugins/alerting/server/routes/disable.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/disable.ts @@ -6,11 +6,11 @@ */ import { schema } from '@kbn/config-schema'; -import type { AlertingRouter } from '../types'; -import { ILicenseState } from '../lib/license_state'; -import { verifyApiAccess } from '../lib/license_api_access'; -import { BASE_ALERT_API_PATH } from '../../common'; -import { AlertTypeDisabledError } from '../lib/errors/alert_type_disabled'; +import type { AlertingRouter } from '../../types'; +import { ILicenseState } from '../../lib/license_state'; +import { verifyApiAccess } from '../../lib/license_api_access'; +import { LEGACY_BASE_ALERT_API_PATH } from '../../../common'; +import { AlertTypeDisabledError } from '../../lib/errors/alert_type_disabled'; const paramSchema = schema.object({ id: schema.string(), @@ -19,7 +19,7 @@ const paramSchema = schema.object({ export const disableAlertRoute = (router: AlertingRouter, licenseState: ILicenseState) => { router.post( { - path: `${BASE_ALERT_API_PATH}/alert/{id}/_disable`, + path: `${LEGACY_BASE_ALERT_API_PATH}/alert/{id}/_disable`, validate: { params: paramSchema, }, diff --git a/x-pack/plugins/alerting/server/routes/enable.test.ts b/x-pack/plugins/alerting/server/routes/legacy/enable.test.ts similarity index 86% rename from x-pack/plugins/alerting/server/routes/enable.test.ts rename to x-pack/plugins/alerting/server/routes/legacy/enable.test.ts index c12a19fc35dbeb..88229472a29364 100644 --- a/x-pack/plugins/alerting/server/routes/enable.test.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/enable.test.ts @@ -7,14 +7,14 @@ import { enableAlertRoute } from './enable'; import { httpServiceMock } from 'src/core/server/mocks'; -import { licenseStateMock } from '../lib/license_state.mock'; -import { mockHandlerArguments } from './_mock_handler_arguments'; -import { alertsClientMock } from '../alerts_client.mock'; -import { AlertTypeDisabledError } from '../lib/errors/alert_type_disabled'; +import { licenseStateMock } from '../../lib/license_state.mock'; +import { mockHandlerArguments } from './../_mock_handler_arguments'; +import { alertsClientMock } from '../../alerts_client.mock'; +import { AlertTypeDisabledError } from '../../lib/errors/alert_type_disabled'; const alertsClient = alertsClientMock.create(); -jest.mock('../lib/license_api_access.ts', () => ({ +jest.mock('../../lib/license_api_access.ts', () => ({ verifyApiAccess: jest.fn(), })); diff --git a/x-pack/plugins/alerting/server/routes/enable.ts b/x-pack/plugins/alerting/server/routes/legacy/enable.ts similarity index 72% rename from x-pack/plugins/alerting/server/routes/enable.ts rename to x-pack/plugins/alerting/server/routes/legacy/enable.ts index d874e9b6106b9b..fcf9ceb8a058bf 100644 --- a/x-pack/plugins/alerting/server/routes/enable.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/enable.ts @@ -6,12 +6,12 @@ */ import { schema } from '@kbn/config-schema'; -import type { AlertingRouter } from '../types'; -import { ILicenseState } from '../lib/license_state'; -import { verifyApiAccess } from '../lib/license_api_access'; -import { BASE_ALERT_API_PATH } from '../../common'; -import { handleDisabledApiKeysError } from './lib/error_handler'; -import { AlertTypeDisabledError } from '../lib/errors/alert_type_disabled'; +import type { AlertingRouter } from '../../types'; +import { ILicenseState } from '../../lib/license_state'; +import { verifyApiAccess } from '../../lib/license_api_access'; +import { LEGACY_BASE_ALERT_API_PATH } from '../../../common'; +import { handleDisabledApiKeysError } from './../lib/error_handler'; +import { AlertTypeDisabledError } from '../../lib/errors/alert_type_disabled'; const paramSchema = schema.object({ id: schema.string(), @@ -20,7 +20,7 @@ const paramSchema = schema.object({ export const enableAlertRoute = (router: AlertingRouter, licenseState: ILicenseState) => { router.post( { - path: `${BASE_ALERT_API_PATH}/alert/{id}/_enable`, + path: `${LEGACY_BASE_ALERT_API_PATH}/alert/{id}/_enable`, validate: { params: paramSchema, }, diff --git a/x-pack/plugins/alerting/server/routes/find.test.ts b/x-pack/plugins/alerting/server/routes/legacy/find.test.ts similarity index 91% rename from x-pack/plugins/alerting/server/routes/find.test.ts rename to x-pack/plugins/alerting/server/routes/legacy/find.test.ts index 791e2babc062e3..640451afcca972 100644 --- a/x-pack/plugins/alerting/server/routes/find.test.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/find.test.ts @@ -7,14 +7,14 @@ import { findAlertRoute } from './find'; import { httpServiceMock } from 'src/core/server/mocks'; -import { licenseStateMock } from '../lib/license_state.mock'; -import { verifyApiAccess } from '../lib/license_api_access'; -import { mockHandlerArguments } from './_mock_handler_arguments'; -import { alertsClientMock } from '../alerts_client.mock'; +import { licenseStateMock } from '../../lib/license_state.mock'; +import { verifyApiAccess } from '../../lib/license_api_access'; +import { mockHandlerArguments } from './../_mock_handler_arguments'; +import { alertsClientMock } from '../../alerts_client.mock'; const alertsClient = alertsClientMock.create(); -jest.mock('../lib/license_api_access.ts', () => ({ +jest.mock('../../lib/license_api_access.ts', () => ({ verifyApiAccess: jest.fn(), })); diff --git a/x-pack/plugins/alerting/server/routes/find.ts b/x-pack/plugins/alerting/server/routes/legacy/find.ts similarity index 86% rename from x-pack/plugins/alerting/server/routes/find.ts rename to x-pack/plugins/alerting/server/routes/legacy/find.ts index ad345de6852662..1d54df53d883ab 100644 --- a/x-pack/plugins/alerting/server/routes/find.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/find.ts @@ -6,13 +6,13 @@ */ import { schema } from '@kbn/config-schema'; -import type { AlertingRouter } from '../types'; +import type { AlertingRouter } from '../../types'; -import { ILicenseState } from '../lib/license_state'; -import { verifyApiAccess } from '../lib/license_api_access'; -import { BASE_ALERT_API_PATH } from '../../common'; -import { renameKeys } from './lib/rename_keys'; -import { FindOptions } from '../alerts_client'; +import { ILicenseState } from '../../lib/license_state'; +import { verifyApiAccess } from '../../lib/license_api_access'; +import { LEGACY_BASE_ALERT_API_PATH } from '../../../common'; +import { renameKeys } from './../lib/rename_keys'; +import { FindOptions } from '../../alerts_client'; // config definition const querySchema = schema.object({ @@ -42,7 +42,7 @@ const querySchema = schema.object({ export const findAlertRoute = (router: AlertingRouter, licenseState: ILicenseState) => { router.get( { - path: `${BASE_ALERT_API_PATH}/_find`, + path: `${LEGACY_BASE_ALERT_API_PATH}/_find`, validate: { query: querySchema, }, diff --git a/x-pack/plugins/alerting/server/routes/get.test.ts b/x-pack/plugins/alerting/server/routes/legacy/get.test.ts similarity index 90% rename from x-pack/plugins/alerting/server/routes/get.test.ts rename to x-pack/plugins/alerting/server/routes/legacy/get.test.ts index 79938e572dfcf1..b2704cdd234f12 100644 --- a/x-pack/plugins/alerting/server/routes/get.test.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/get.test.ts @@ -7,14 +7,14 @@ import { getAlertRoute } from './get'; import { httpServiceMock } from 'src/core/server/mocks'; -import { licenseStateMock } from '../lib/license_state.mock'; -import { verifyApiAccess } from '../lib/license_api_access'; -import { mockHandlerArguments } from './_mock_handler_arguments'; -import { alertsClientMock } from '../alerts_client.mock'; -import { Alert } from '../../common'; +import { licenseStateMock } from '../../lib/license_state.mock'; +import { verifyApiAccess } from '../../lib/license_api_access'; +import { mockHandlerArguments } from './../_mock_handler_arguments'; +import { alertsClientMock } from '../../alerts_client.mock'; +import { Alert } from '../../../common'; const alertsClient = alertsClientMock.create(); -jest.mock('../lib/license_api_access.ts', () => ({ +jest.mock('../../lib/license_api_access.ts', () => ({ verifyApiAccess: jest.fn(), })); diff --git a/x-pack/plugins/alerting/server/routes/get.ts b/x-pack/plugins/alerting/server/routes/legacy/get.ts similarity index 76% rename from x-pack/plugins/alerting/server/routes/get.ts rename to x-pack/plugins/alerting/server/routes/legacy/get.ts index 93d2f29f53c18d..cf63a4387a93ee 100644 --- a/x-pack/plugins/alerting/server/routes/get.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/get.ts @@ -6,10 +6,10 @@ */ import { schema } from '@kbn/config-schema'; -import { ILicenseState } from '../lib/license_state'; -import { verifyApiAccess } from '../lib/license_api_access'; -import { BASE_ALERT_API_PATH } from '../../common'; -import type { AlertingRouter } from '../types'; +import { ILicenseState } from '../../lib/license_state'; +import { verifyApiAccess } from '../../lib/license_api_access'; +import { LEGACY_BASE_ALERT_API_PATH } from '../../../common'; +import type { AlertingRouter } from '../../types'; const paramSchema = schema.object({ id: schema.string(), @@ -18,7 +18,7 @@ const paramSchema = schema.object({ export const getAlertRoute = (router: AlertingRouter, licenseState: ILicenseState) => { router.get( { - path: `${BASE_ALERT_API_PATH}/alert/{id}`, + path: `${LEGACY_BASE_ALERT_API_PATH}/alert/{id}`, validate: { params: paramSchema, }, diff --git a/x-pack/plugins/alerting/server/routes/get_alert_instance_summary.test.ts b/x-pack/plugins/alerting/server/routes/legacy/get_alert_instance_summary.test.ts similarity index 89% rename from x-pack/plugins/alerting/server/routes/get_alert_instance_summary.test.ts rename to x-pack/plugins/alerting/server/routes/legacy/get_alert_instance_summary.test.ts index 3157f18afbb95b..0162ef91f55e7e 100644 --- a/x-pack/plugins/alerting/server/routes/get_alert_instance_summary.test.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/get_alert_instance_summary.test.ts @@ -7,14 +7,14 @@ import { getAlertInstanceSummaryRoute } from './get_alert_instance_summary'; import { httpServiceMock } from 'src/core/server/mocks'; -import { licenseStateMock } from '../lib/license_state.mock'; -import { mockHandlerArguments } from './_mock_handler_arguments'; +import { licenseStateMock } from '../../lib/license_state.mock'; +import { mockHandlerArguments } from './../_mock_handler_arguments'; import { SavedObjectsErrorHelpers } from 'src/core/server'; -import { alertsClientMock } from '../alerts_client.mock'; -import { AlertInstanceSummary } from '../types'; +import { alertsClientMock } from '../../alerts_client.mock'; +import { AlertInstanceSummary } from '../../types'; const alertsClient = alertsClientMock.create(); -jest.mock('../lib/license_api_access.ts', () => ({ +jest.mock('../../lib/license_api_access.ts', () => ({ verifyApiAccess: jest.fn(), })); diff --git a/x-pack/plugins/alerting/server/routes/get_alert_instance_summary.ts b/x-pack/plugins/alerting/server/routes/legacy/get_alert_instance_summary.ts similarity index 79% rename from x-pack/plugins/alerting/server/routes/get_alert_instance_summary.ts rename to x-pack/plugins/alerting/server/routes/legacy/get_alert_instance_summary.ts index 71c85caa38c8d4..00c96197f6f9be 100644 --- a/x-pack/plugins/alerting/server/routes/get_alert_instance_summary.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/get_alert_instance_summary.ts @@ -6,10 +6,10 @@ */ import { schema } from '@kbn/config-schema'; -import type { AlertingRouter } from '../types'; -import { ILicenseState } from '../lib/license_state'; -import { verifyApiAccess } from '../lib/license_api_access'; -import { BASE_ALERT_API_PATH } from '../../common'; +import type { AlertingRouter } from '../../types'; +import { ILicenseState } from '../../lib/license_state'; +import { verifyApiAccess } from '../../lib/license_api_access'; +import { LEGACY_BASE_ALERT_API_PATH } from '../../../common'; const paramSchema = schema.object({ id: schema.string(), @@ -25,7 +25,7 @@ export const getAlertInstanceSummaryRoute = ( ) => { router.get( { - path: `${BASE_ALERT_API_PATH}/alert/{id}/_instance_summary`, + path: `${LEGACY_BASE_ALERT_API_PATH}/alert/{id}/_instance_summary`, validate: { params: paramSchema, query: querySchema, diff --git a/x-pack/plugins/alerting/server/routes/get_alert_state.test.ts b/x-pack/plugins/alerting/server/routes/legacy/get_alert_state.test.ts similarity index 93% rename from x-pack/plugins/alerting/server/routes/get_alert_state.test.ts rename to x-pack/plugins/alerting/server/routes/legacy/get_alert_state.test.ts index 5aa96d6c734470..f08e0992cbba04 100644 --- a/x-pack/plugins/alerting/server/routes/get_alert_state.test.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/get_alert_state.test.ts @@ -7,13 +7,13 @@ import { getAlertStateRoute } from './get_alert_state'; import { httpServiceMock } from 'src/core/server/mocks'; -import { licenseStateMock } from '../lib/license_state.mock'; -import { mockHandlerArguments } from './_mock_handler_arguments'; +import { licenseStateMock } from '../../lib/license_state.mock'; +import { mockHandlerArguments } from './../_mock_handler_arguments'; import { SavedObjectsErrorHelpers } from 'src/core/server'; -import { alertsClientMock } from '../alerts_client.mock'; +import { alertsClientMock } from '../../alerts_client.mock'; const alertsClient = alertsClientMock.create(); -jest.mock('../lib/license_api_access.ts', () => ({ +jest.mock('../../lib/license_api_access.ts', () => ({ verifyApiAccess: jest.fn(), })); diff --git a/x-pack/plugins/alerting/server/routes/get_alert_state.ts b/x-pack/plugins/alerting/server/routes/legacy/get_alert_state.ts similarity index 77% rename from x-pack/plugins/alerting/server/routes/get_alert_state.ts rename to x-pack/plugins/alerting/server/routes/legacy/get_alert_state.ts index 3a05946162d37e..5e7cbfbe6eb952 100644 --- a/x-pack/plugins/alerting/server/routes/get_alert_state.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/get_alert_state.ts @@ -6,10 +6,10 @@ */ import { schema } from '@kbn/config-schema'; -import type { AlertingRouter } from '../types'; -import { ILicenseState } from '../lib/license_state'; -import { verifyApiAccess } from '../lib/license_api_access'; -import { BASE_ALERT_API_PATH } from '../../common'; +import type { AlertingRouter } from '../../types'; +import { ILicenseState } from '../../lib/license_state'; +import { verifyApiAccess } from '../../lib/license_api_access'; +import { LEGACY_BASE_ALERT_API_PATH } from '../../../common'; const paramSchema = schema.object({ id: schema.string(), @@ -18,7 +18,7 @@ const paramSchema = schema.object({ export const getAlertStateRoute = (router: AlertingRouter, licenseState: ILicenseState) => { router.get( { - path: `${BASE_ALERT_API_PATH}/alert/{id}/state`, + path: `${LEGACY_BASE_ALERT_API_PATH}/alert/{id}/state`, validate: { params: paramSchema, }, diff --git a/x-pack/plugins/alerting/server/routes/legacy/health.test.ts b/x-pack/plugins/alerting/server/routes/legacy/health.test.ts new file mode 100644 index 00000000000000..74de5f70a32e77 --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/legacy/health.test.ts @@ -0,0 +1,336 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { healthRoute } from './health'; +import { httpServiceMock } from 'src/core/server/mocks'; +import { mockHandlerArguments } from './../_mock_handler_arguments'; +import { elasticsearchServiceMock } from '../../../../../../src/core/server/mocks'; +import { verifyApiAccess } from '../../lib/license_api_access'; +import { licenseStateMock } from '../../lib/license_state.mock'; +import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/server/mocks'; +import { alertsClientMock } from '../../alerts_client.mock'; +import { HealthStatus } from '../../types'; +import { alertsMock } from '../../mocks'; +const alertsClient = alertsClientMock.create(); + +jest.mock('../../lib/license_api_access.ts', () => ({ + verifyApiAccess: jest.fn(), +})); + +const alerting = alertsMock.createStart(); + +const currentDate = new Date().toISOString(); +beforeEach(() => { + jest.resetAllMocks(); + alerting.getFrameworkHealth.mockResolvedValue({ + decryptionHealth: { + status: HealthStatus.OK, + timestamp: currentDate, + }, + executionHealth: { + status: HealthStatus.OK, + timestamp: currentDate, + }, + readHealth: { + status: HealthStatus.OK, + timestamp: currentDate, + }, + }); +}); + +describe('healthRoute', () => { + it('registers the route', async () => { + const router = httpServiceMock.createRouter(); + + const licenseState = licenseStateMock.create(); + const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup({ canEncrypt: true }); + healthRoute(router, licenseState, encryptedSavedObjects); + + const [config] = router.get.mock.calls[0]; + + expect(config.path).toMatchInlineSnapshot(`"/api/alerts/_health"`); + }); + + it('queries the usage api', async () => { + const router = httpServiceMock.createRouter(); + + const licenseState = licenseStateMock.create(); + const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup({ canEncrypt: true }); + healthRoute(router, licenseState, encryptedSavedObjects); + const [, handler] = router.get.mock.calls[0]; + + const esClient = elasticsearchServiceMock.createScopedClusterClient(); + esClient.asInternalUser.transport.request.mockReturnValue( + elasticsearchServiceMock.createSuccessTransportRequestPromise({}) + ); + + const [context, req, res] = mockHandlerArguments({ esClient, alertsClient }, {}, ['ok']); + + await handler(context, req, res); + + expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); + + expect(esClient.asInternalUser.transport.request.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "method": "GET", + "path": "/_xpack/usage", + }, + ] + `); + }); + + it('evaluates whether Encrypted Saved Objects is missing encryption key', async () => { + const router = httpServiceMock.createRouter(); + + const licenseState = licenseStateMock.create(); + const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup({ canEncrypt: false }); + healthRoute(router, licenseState, encryptedSavedObjects); + const [, handler] = router.get.mock.calls[0]; + + const esClient = elasticsearchServiceMock.createScopedClusterClient(); + esClient.asInternalUser.transport.request.mockReturnValue( + elasticsearchServiceMock.createSuccessTransportRequestPromise({}) + ); + + const [context, req, res] = mockHandlerArguments( + { esClient, alertsClient, getFrameworkHealth: alerting.getFrameworkHealth }, + {}, + ['ok'] + ); + + expect(await handler(context, req, res)).toStrictEqual({ + body: { + alertingFrameworkHeath: { + decryptionHealth: { + status: HealthStatus.OK, + timestamp: currentDate, + }, + executionHealth: { + status: HealthStatus.OK, + timestamp: currentDate, + }, + readHealth: { + status: HealthStatus.OK, + timestamp: currentDate, + }, + }, + hasPermanentEncryptionKey: false, + isSufficientlySecure: true, + }, + }); + }); + + it('evaluates missing security info from the usage api to mean that the security plugin is disbled', async () => { + const router = httpServiceMock.createRouter(); + + const licenseState = licenseStateMock.create(); + const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup({ canEncrypt: true }); + healthRoute(router, licenseState, encryptedSavedObjects); + const [, handler] = router.get.mock.calls[0]; + + const esClient = elasticsearchServiceMock.createScopedClusterClient(); + esClient.asInternalUser.transport.request.mockReturnValue( + elasticsearchServiceMock.createSuccessTransportRequestPromise({}) + ); + + const [context, req, res] = mockHandlerArguments( + { esClient, alertsClient, getFrameworkHealth: alerting.getFrameworkHealth }, + {}, + ['ok'] + ); + + expect(await handler(context, req, res)).toStrictEqual({ + body: { + alertingFrameworkHeath: { + decryptionHealth: { + status: HealthStatus.OK, + timestamp: currentDate, + }, + executionHealth: { + status: HealthStatus.OK, + timestamp: currentDate, + }, + readHealth: { + status: HealthStatus.OK, + timestamp: currentDate, + }, + }, + hasPermanentEncryptionKey: true, + isSufficientlySecure: true, + }, + }); + }); + + it('evaluates missing security http info from the usage api to mean that the security plugin is disbled', async () => { + const router = httpServiceMock.createRouter(); + + const licenseState = licenseStateMock.create(); + const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup({ canEncrypt: true }); + healthRoute(router, licenseState, encryptedSavedObjects); + const [, handler] = router.get.mock.calls[0]; + + const esClient = elasticsearchServiceMock.createScopedClusterClient(); + esClient.asInternalUser.transport.request.mockReturnValue( + elasticsearchServiceMock.createSuccessTransportRequestPromise({}) + ); + + const [context, req, res] = mockHandlerArguments( + { esClient, alertsClient, getFrameworkHealth: alerting.getFrameworkHealth }, + {}, + ['ok'] + ); + + expect(await handler(context, req, res)).toStrictEqual({ + body: { + alertingFrameworkHeath: { + decryptionHealth: { + status: HealthStatus.OK, + timestamp: currentDate, + }, + executionHealth: { + status: HealthStatus.OK, + timestamp: currentDate, + }, + readHealth: { + status: HealthStatus.OK, + timestamp: currentDate, + }, + }, + hasPermanentEncryptionKey: true, + isSufficientlySecure: true, + }, + }); + }); + + it('evaluates security enabled, and missing ssl info from the usage api to mean that the user cannot generate keys', async () => { + const router = httpServiceMock.createRouter(); + + const licenseState = licenseStateMock.create(); + const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup({ canEncrypt: true }); + healthRoute(router, licenseState, encryptedSavedObjects); + const [, handler] = router.get.mock.calls[0]; + + const esClient = elasticsearchServiceMock.createScopedClusterClient(); + esClient.asInternalUser.transport.request.mockReturnValue( + elasticsearchServiceMock.createSuccessTransportRequestPromise({ security: { enabled: true } }) + ); + + const [context, req, res] = mockHandlerArguments( + { esClient, alertsClient, getFrameworkHealth: alerting.getFrameworkHealth }, + {}, + ['ok'] + ); + + expect(await handler(context, req, res)).toStrictEqual({ + body: { + alertingFrameworkHeath: { + decryptionHealth: { + status: HealthStatus.OK, + timestamp: currentDate, + }, + executionHealth: { + status: HealthStatus.OK, + timestamp: currentDate, + }, + readHealth: { + status: HealthStatus.OK, + timestamp: currentDate, + }, + }, + hasPermanentEncryptionKey: true, + isSufficientlySecure: false, + }, + }); + }); + + it('evaluates security enabled, SSL info present but missing http info from the usage api to mean that the user cannot generate keys', async () => { + const router = httpServiceMock.createRouter(); + + const licenseState = licenseStateMock.create(); + const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup({ canEncrypt: true }); + healthRoute(router, licenseState, encryptedSavedObjects); + const [, handler] = router.get.mock.calls[0]; + + const esClient = elasticsearchServiceMock.createScopedClusterClient(); + esClient.asInternalUser.transport.request.mockReturnValue( + elasticsearchServiceMock.createSuccessTransportRequestPromise({ + security: { enabled: true, ssl: {} }, + }) + ); + + const [context, req, res] = mockHandlerArguments( + { esClient, alertsClient, getFrameworkHealth: alerting.getFrameworkHealth }, + {}, + ['ok'] + ); + + expect(await handler(context, req, res)).toStrictEqual({ + body: { + alertingFrameworkHeath: { + decryptionHealth: { + status: HealthStatus.OK, + timestamp: currentDate, + }, + executionHealth: { + status: HealthStatus.OK, + timestamp: currentDate, + }, + readHealth: { + status: HealthStatus.OK, + timestamp: currentDate, + }, + }, + hasPermanentEncryptionKey: true, + isSufficientlySecure: false, + }, + }); + }); + + it('evaluates security and tls enabled to mean that the user can generate keys', async () => { + const router = httpServiceMock.createRouter(); + + const licenseState = licenseStateMock.create(); + const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup({ canEncrypt: true }); + healthRoute(router, licenseState, encryptedSavedObjects); + const [, handler] = router.get.mock.calls[0]; + + const esClient = elasticsearchServiceMock.createScopedClusterClient(); + esClient.asInternalUser.transport.request.mockReturnValue( + elasticsearchServiceMock.createSuccessTransportRequestPromise({ + security: { enabled: true, ssl: { http: { enabled: true } } }, + }) + ); + + const [context, req, res] = mockHandlerArguments( + { esClient, alertsClient, getFrameworkHealth: alerting.getFrameworkHealth }, + {}, + ['ok'] + ); + + expect(await handler(context, req, res)).toStrictEqual({ + body: { + alertingFrameworkHeath: { + decryptionHealth: { + status: HealthStatus.OK, + timestamp: currentDate, + }, + executionHealth: { + status: HealthStatus.OK, + timestamp: currentDate, + }, + readHealth: { + status: HealthStatus.OK, + timestamp: currentDate, + }, + }, + hasPermanentEncryptionKey: true, + isSufficientlySecure: true, + }, + }); + }); +}); diff --git a/x-pack/plugins/alerting/server/routes/legacy/health.ts b/x-pack/plugins/alerting/server/routes/legacy/health.ts new file mode 100644 index 00000000000000..b9906a56ce9724 --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/legacy/health.ts @@ -0,0 +1,71 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ApiResponse } from '@elastic/elasticsearch'; +import type { AlertingRouter } from '../../types'; +import { ILicenseState } from '../../lib/license_state'; +import { verifyApiAccess } from '../../lib/license_api_access'; +import { AlertingFrameworkHealth } from '../../types'; +import { EncryptedSavedObjectsPluginSetup } from '../../../../encrypted_saved_objects/server'; + +interface XPackUsageSecurity { + security?: { + enabled?: boolean; + ssl?: { + http?: { + enabled?: boolean; + }; + }; + }; +} + +export function healthRoute( + router: AlertingRouter, + licenseState: ILicenseState, + encryptedSavedObjects: EncryptedSavedObjectsPluginSetup +) { + router.get( + { + path: '/api/alerts/_health', + validate: false, + }, + router.handleLegacyErrors(async function (context, req, res) { + verifyApiAccess(licenseState); + if (!context.alerting) { + return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' }); + } + try { + const { + body: { + security: { + enabled: isSecurityEnabled = false, + ssl: { http: { enabled: isTLSEnabled = false } = {} } = {}, + } = {}, + }, + }: ApiResponse = await context.core.elasticsearch.client.asInternalUser.transport // Do not augment with such input. // `transport.request` is potentially unsafe when combined with untrusted user input. + .request({ + method: 'GET', + path: '/_xpack/usage', + }); + + const alertingFrameworkHeath = await context.alerting.getFrameworkHealth(); + + const frameworkHealth: AlertingFrameworkHealth = { + isSufficientlySecure: !isSecurityEnabled || (isSecurityEnabled && isTLSEnabled), + hasPermanentEncryptionKey: encryptedSavedObjects.canEncrypt, + alertingFrameworkHeath, + }; + + return res.ok({ + body: frameworkHealth, + }); + } catch (error) { + return res.badRequest({ body: error }); + } + }) + ); +} diff --git a/x-pack/plugins/alerting/server/routes/legacy/index.ts b/x-pack/plugins/alerting/server/routes/legacy/index.ts new file mode 100644 index 00000000000000..d1b2f9784f24d5 --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/legacy/index.ts @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { IRouter } from 'kibana/server'; +import { ILicenseState } from '../../lib'; +import { AlertingRequestHandlerContext } from '../../types'; +import { EncryptedSavedObjectsPluginSetup } from '../../../../encrypted_saved_objects/server'; + +import { aggregateAlertRoute } from './aggregate'; +import { createAlertRoute } from './create'; +import { deleteAlertRoute } from './delete'; +import { findAlertRoute } from './find'; +import { getAlertRoute } from './get'; +import { getAlertStateRoute } from './get_alert_state'; +import { getAlertInstanceSummaryRoute } from './get_alert_instance_summary'; +import { listAlertTypesRoute } from './list_alert_types'; +import { updateAlertRoute } from './update'; +import { enableAlertRoute } from './enable'; +import { disableAlertRoute } from './disable'; +import { updateApiKeyRoute } from './update_api_key'; +import { muteAlertInstanceRoute } from './mute_instance'; +import { unmuteAlertInstanceRoute } from './unmute_instance'; +import { muteAllAlertRoute } from './mute_all'; +import { unmuteAllAlertRoute } from './unmute_all'; +import { healthRoute } from './health'; + +export function defineLegacyRoutes( + router: IRouter, + licenseState: ILicenseState, + encryptedSavedObjects: EncryptedSavedObjectsPluginSetup +) { + aggregateAlertRoute(router, licenseState); + createAlertRoute(router, licenseState); + deleteAlertRoute(router, licenseState); + findAlertRoute(router, licenseState); + getAlertRoute(router, licenseState); + getAlertStateRoute(router, licenseState); + getAlertInstanceSummaryRoute(router, licenseState); + listAlertTypesRoute(router, licenseState); + updateAlertRoute(router, licenseState); + enableAlertRoute(router, licenseState); + disableAlertRoute(router, licenseState); + updateApiKeyRoute(router, licenseState); + muteAllAlertRoute(router, licenseState); + unmuteAllAlertRoute(router, licenseState); + muteAlertInstanceRoute(router, licenseState); + unmuteAlertInstanceRoute(router, licenseState); + healthRoute(router, licenseState, encryptedSavedObjects); +} diff --git a/x-pack/plugins/alerting/server/routes/list_alert_types.test.ts b/x-pack/plugins/alerting/server/routes/legacy/list_alert_types.test.ts similarity index 92% rename from x-pack/plugins/alerting/server/routes/list_alert_types.test.ts rename to x-pack/plugins/alerting/server/routes/legacy/list_alert_types.test.ts index 5ef2e8cc9ec7d5..3e6f2f484a6d87 100644 --- a/x-pack/plugins/alerting/server/routes/list_alert_types.test.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/list_alert_types.test.ts @@ -7,16 +7,16 @@ import { listAlertTypesRoute } from './list_alert_types'; import { httpServiceMock } from 'src/core/server/mocks'; -import { licenseStateMock } from '../lib/license_state.mock'; -import { verifyApiAccess } from '../lib/license_api_access'; -import { mockHandlerArguments } from './_mock_handler_arguments'; -import { alertsClientMock } from '../alerts_client.mock'; -import { RecoveredActionGroup } from '../../common'; -import { RegistryAlertTypeWithAuth } from '../authorization'; +import { licenseStateMock } from '../../lib/license_state.mock'; +import { verifyApiAccess } from '../../lib/license_api_access'; +import { mockHandlerArguments } from './../_mock_handler_arguments'; +import { alertsClientMock } from '../../alerts_client.mock'; +import { RecoveredActionGroup } from '../../../common'; +import { RegistryAlertTypeWithAuth } from '../../authorization'; const alertsClient = alertsClientMock.create(); -jest.mock('../lib/license_api_access.ts', () => ({ +jest.mock('../../lib/license_api_access.ts', () => ({ verifyApiAccess: jest.fn(), })); diff --git a/x-pack/plugins/alerting/server/routes/list_alert_types.ts b/x-pack/plugins/alerting/server/routes/legacy/list_alert_types.ts similarity index 72% rename from x-pack/plugins/alerting/server/routes/list_alert_types.ts rename to x-pack/plugins/alerting/server/routes/legacy/list_alert_types.ts index 2040f71f8ad054..da41cc487228c8 100644 --- a/x-pack/plugins/alerting/server/routes/list_alert_types.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/list_alert_types.ts @@ -5,15 +5,15 @@ * 2.0. */ -import type { AlertingRouter } from '../types'; -import { ILicenseState } from '../lib/license_state'; -import { verifyApiAccess } from '../lib/license_api_access'; -import { BASE_ALERT_API_PATH } from '../../common'; +import type { AlertingRouter } from '../../types'; +import { ILicenseState } from '../../lib/license_state'; +import { verifyApiAccess } from '../../lib/license_api_access'; +import { LEGACY_BASE_ALERT_API_PATH } from '../../../common'; export const listAlertTypesRoute = (router: AlertingRouter, licenseState: ILicenseState) => { router.get( { - path: `${BASE_ALERT_API_PATH}/list_alert_types`, + path: `${LEGACY_BASE_ALERT_API_PATH}/list_alert_types`, validate: {}, }, router.handleLegacyErrors(async function (context, req, res) { diff --git a/x-pack/plugins/alerting/server/routes/mute_all.test.ts b/x-pack/plugins/alerting/server/routes/legacy/mute_all.test.ts similarity index 86% rename from x-pack/plugins/alerting/server/routes/mute_all.test.ts rename to x-pack/plugins/alerting/server/routes/legacy/mute_all.test.ts index 8c938d71ab06c2..ffe893fa604905 100644 --- a/x-pack/plugins/alerting/server/routes/mute_all.test.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/mute_all.test.ts @@ -7,13 +7,13 @@ import { muteAllAlertRoute } from './mute_all'; import { httpServiceMock } from 'src/core/server/mocks'; -import { licenseStateMock } from '../lib/license_state.mock'; -import { mockHandlerArguments } from './_mock_handler_arguments'; -import { alertsClientMock } from '../alerts_client.mock'; -import { AlertTypeDisabledError } from '../lib/errors/alert_type_disabled'; +import { licenseStateMock } from '../../lib/license_state.mock'; +import { mockHandlerArguments } from './../_mock_handler_arguments'; +import { alertsClientMock } from '../../alerts_client.mock'; +import { AlertTypeDisabledError } from '../../lib/errors/alert_type_disabled'; const alertsClient = alertsClientMock.create(); -jest.mock('../lib/license_api_access.ts', () => ({ +jest.mock('../../lib/license_api_access.ts', () => ({ verifyApiAccess: jest.fn(), })); diff --git a/x-pack/plugins/alerting/server/routes/mute_all.ts b/x-pack/plugins/alerting/server/routes/legacy/mute_all.ts similarity index 74% rename from x-pack/plugins/alerting/server/routes/mute_all.ts rename to x-pack/plugins/alerting/server/routes/legacy/mute_all.ts index fee1fdfe5a4f7b..643aeb97084a81 100644 --- a/x-pack/plugins/alerting/server/routes/mute_all.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/mute_all.ts @@ -6,11 +6,11 @@ */ import { schema } from '@kbn/config-schema'; -import type { AlertingRouter } from '../types'; -import { ILicenseState } from '../lib/license_state'; -import { verifyApiAccess } from '../lib/license_api_access'; -import { BASE_ALERT_API_PATH } from '../../common'; -import { AlertTypeDisabledError } from '../lib/errors/alert_type_disabled'; +import type { AlertingRouter } from '../../types'; +import { ILicenseState } from '../../lib/license_state'; +import { verifyApiAccess } from '../../lib/license_api_access'; +import { LEGACY_BASE_ALERT_API_PATH } from '../../../common'; +import { AlertTypeDisabledError } from '../../lib/errors/alert_type_disabled'; const paramSchema = schema.object({ id: schema.string(), @@ -19,7 +19,7 @@ const paramSchema = schema.object({ export const muteAllAlertRoute = (router: AlertingRouter, licenseState: ILicenseState) => { router.post( { - path: `${BASE_ALERT_API_PATH}/alert/{id}/_mute_all`, + path: `${LEGACY_BASE_ALERT_API_PATH}/alert/{id}/_mute_all`, validate: { params: paramSchema, }, diff --git a/x-pack/plugins/alerting/server/routes/mute_instance.test.ts b/x-pack/plugins/alerting/server/routes/legacy/mute_instance.test.ts similarity index 87% rename from x-pack/plugins/alerting/server/routes/mute_instance.test.ts rename to x-pack/plugins/alerting/server/routes/legacy/mute_instance.test.ts index 83bf57f5259a3f..a00c75663c5f01 100644 --- a/x-pack/plugins/alerting/server/routes/mute_instance.test.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/mute_instance.test.ts @@ -7,13 +7,13 @@ import { muteAlertInstanceRoute } from './mute_instance'; import { httpServiceMock } from 'src/core/server/mocks'; -import { licenseStateMock } from '../lib/license_state.mock'; -import { mockHandlerArguments } from './_mock_handler_arguments'; -import { alertsClientMock } from '../alerts_client.mock'; -import { AlertTypeDisabledError } from '../lib/errors/alert_type_disabled'; +import { licenseStateMock } from '../../lib/license_state.mock'; +import { mockHandlerArguments } from './../_mock_handler_arguments'; +import { alertsClientMock } from '../../alerts_client.mock'; +import { AlertTypeDisabledError } from '../../lib/errors/alert_type_disabled'; const alertsClient = alertsClientMock.create(); -jest.mock('../lib/license_api_access.ts', () => ({ +jest.mock('../../lib/license_api_access.ts', () => ({ verifyApiAccess: jest.fn(), })); diff --git a/x-pack/plugins/alerting/server/routes/mute_instance.ts b/x-pack/plugins/alerting/server/routes/legacy/mute_instance.ts similarity index 72% rename from x-pack/plugins/alerting/server/routes/mute_instance.ts rename to x-pack/plugins/alerting/server/routes/legacy/mute_instance.ts index ab7749178f6cf0..2b35f59c7fce16 100644 --- a/x-pack/plugins/alerting/server/routes/mute_instance.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/mute_instance.ts @@ -6,13 +6,13 @@ */ import { schema } from '@kbn/config-schema'; -import type { AlertingRouter } from '../types'; -import { ILicenseState } from '../lib/license_state'; -import { verifyApiAccess } from '../lib/license_api_access'; -import { BASE_ALERT_API_PATH } from '../../common'; -import { renameKeys } from './lib/rename_keys'; -import { MuteOptions } from '../alerts_client'; -import { AlertTypeDisabledError } from '../lib/errors/alert_type_disabled'; +import type { AlertingRouter } from '../../types'; +import { ILicenseState } from '../../lib/license_state'; +import { verifyApiAccess } from '../../lib/license_api_access'; +import { LEGACY_BASE_ALERT_API_PATH } from '../../../common'; +import { renameKeys } from './../lib/rename_keys'; +import { MuteOptions } from '../../alerts_client'; +import { AlertTypeDisabledError } from '../../lib/errors/alert_type_disabled'; const paramSchema = schema.object({ alert_id: schema.string(), @@ -22,7 +22,7 @@ const paramSchema = schema.object({ export const muteAlertInstanceRoute = (router: AlertingRouter, licenseState: ILicenseState) => { router.post( { - path: `${BASE_ALERT_API_PATH}/alert/{alert_id}/alert_instance/{alert_instance_id}/_mute`, + path: `${LEGACY_BASE_ALERT_API_PATH}/alert/{alert_id}/alert_instance/{alert_instance_id}/_mute`, validate: { params: paramSchema, }, diff --git a/x-pack/plugins/alerting/server/routes/unmute_all.test.ts b/x-pack/plugins/alerting/server/routes/legacy/unmute_all.test.ts similarity index 86% rename from x-pack/plugins/alerting/server/routes/unmute_all.test.ts rename to x-pack/plugins/alerting/server/routes/legacy/unmute_all.test.ts index 0c2d3105e581cc..8511a8b68a4476 100644 --- a/x-pack/plugins/alerting/server/routes/unmute_all.test.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/unmute_all.test.ts @@ -7,13 +7,13 @@ import { unmuteAllAlertRoute } from './unmute_all'; import { httpServiceMock } from 'src/core/server/mocks'; -import { licenseStateMock } from '../lib/license_state.mock'; -import { mockHandlerArguments } from './_mock_handler_arguments'; -import { alertsClientMock } from '../alerts_client.mock'; -import { AlertTypeDisabledError } from '../lib/errors/alert_type_disabled'; +import { licenseStateMock } from '../../lib/license_state.mock'; +import { mockHandlerArguments } from './../_mock_handler_arguments'; +import { alertsClientMock } from '../../alerts_client.mock'; +import { AlertTypeDisabledError } from '../../lib/errors/alert_type_disabled'; const alertsClient = alertsClientMock.create(); -jest.mock('../lib/license_api_access.ts', () => ({ +jest.mock('../../lib/license_api_access.ts', () => ({ verifyApiAccess: jest.fn(), })); diff --git a/x-pack/plugins/alerting/server/routes/unmute_all.ts b/x-pack/plugins/alerting/server/routes/legacy/unmute_all.ts similarity index 74% rename from x-pack/plugins/alerting/server/routes/unmute_all.ts rename to x-pack/plugins/alerting/server/routes/legacy/unmute_all.ts index 2e67884f8a9449..1259428be3329f 100644 --- a/x-pack/plugins/alerting/server/routes/unmute_all.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/unmute_all.ts @@ -6,11 +6,11 @@ */ import { schema } from '@kbn/config-schema'; -import type { AlertingRouter } from '../types'; -import { ILicenseState } from '../lib/license_state'; -import { verifyApiAccess } from '../lib/license_api_access'; -import { BASE_ALERT_API_PATH } from '../../common'; -import { AlertTypeDisabledError } from '../lib/errors/alert_type_disabled'; +import type { AlertingRouter } from '../../types'; +import { ILicenseState } from '../../lib/license_state'; +import { verifyApiAccess } from '../../lib/license_api_access'; +import { LEGACY_BASE_ALERT_API_PATH } from '../../../common'; +import { AlertTypeDisabledError } from '../../lib/errors/alert_type_disabled'; const paramSchema = schema.object({ id: schema.string(), @@ -19,7 +19,7 @@ const paramSchema = schema.object({ export const unmuteAllAlertRoute = (router: AlertingRouter, licenseState: ILicenseState) => { router.post( { - path: `${BASE_ALERT_API_PATH}/alert/{id}/_unmute_all`, + path: `${LEGACY_BASE_ALERT_API_PATH}/alert/{id}/_unmute_all`, validate: { params: paramSchema, }, diff --git a/x-pack/plugins/alerting/server/routes/unmute_instance.test.ts b/x-pack/plugins/alerting/server/routes/legacy/unmute_instance.test.ts similarity index 87% rename from x-pack/plugins/alerting/server/routes/unmute_instance.test.ts rename to x-pack/plugins/alerting/server/routes/legacy/unmute_instance.test.ts index eddd00390a7f33..d28868a3c92301 100644 --- a/x-pack/plugins/alerting/server/routes/unmute_instance.test.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/unmute_instance.test.ts @@ -7,13 +7,13 @@ import { unmuteAlertInstanceRoute } from './unmute_instance'; import { httpServiceMock } from 'src/core/server/mocks'; -import { licenseStateMock } from '../lib/license_state.mock'; -import { mockHandlerArguments } from './_mock_handler_arguments'; -import { alertsClientMock } from '../alerts_client.mock'; -import { AlertTypeDisabledError } from '../lib/errors/alert_type_disabled'; +import { licenseStateMock } from '../../lib/license_state.mock'; +import { mockHandlerArguments } from './../_mock_handler_arguments'; +import { alertsClientMock } from '../../alerts_client.mock'; +import { AlertTypeDisabledError } from '../../lib/errors/alert_type_disabled'; const alertsClient = alertsClientMock.create(); -jest.mock('../lib/license_api_access.ts', () => ({ +jest.mock('../../lib/license_api_access.ts', () => ({ verifyApiAccess: jest.fn(), })); diff --git a/x-pack/plugins/alerting/server/routes/unmute_instance.ts b/x-pack/plugins/alerting/server/routes/legacy/unmute_instance.ts similarity index 74% rename from x-pack/plugins/alerting/server/routes/unmute_instance.ts rename to x-pack/plugins/alerting/server/routes/legacy/unmute_instance.ts index d39fc696eef082..25f5aa654bde14 100644 --- a/x-pack/plugins/alerting/server/routes/unmute_instance.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/unmute_instance.ts @@ -6,11 +6,11 @@ */ import { schema } from '@kbn/config-schema'; -import type { AlertingRouter } from '../types'; -import { ILicenseState } from '../lib/license_state'; -import { verifyApiAccess } from '../lib/license_api_access'; -import { BASE_ALERT_API_PATH } from '../../common'; -import { AlertTypeDisabledError } from '../lib/errors/alert_type_disabled'; +import type { AlertingRouter } from '../../types'; +import { ILicenseState } from '../../lib/license_state'; +import { verifyApiAccess } from '../../lib/license_api_access'; +import { LEGACY_BASE_ALERT_API_PATH } from '../../../common'; +import { AlertTypeDisabledError } from '../../lib/errors/alert_type_disabled'; const paramSchema = schema.object({ alertId: schema.string(), @@ -20,7 +20,7 @@ const paramSchema = schema.object({ export const unmuteAlertInstanceRoute = (router: AlertingRouter, licenseState: ILicenseState) => { router.post( { - path: `${BASE_ALERT_API_PATH}/alert/{alertId}/alert_instance/{alertInstanceId}/_unmute`, + path: `${LEGACY_BASE_ALERT_API_PATH}/alert/{alertId}/alert_instance/{alertInstanceId}/_unmute`, validate: { params: paramSchema, }, diff --git a/x-pack/plugins/alerting/server/routes/update.test.ts b/x-pack/plugins/alerting/server/routes/legacy/update.test.ts similarity index 92% rename from x-pack/plugins/alerting/server/routes/update.test.ts rename to x-pack/plugins/alerting/server/routes/legacy/update.test.ts index 892f1a25844c4d..da58d0dc41522e 100644 --- a/x-pack/plugins/alerting/server/routes/update.test.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/update.test.ts @@ -7,15 +7,15 @@ import { updateAlertRoute } from './update'; import { httpServiceMock } from 'src/core/server/mocks'; -import { licenseStateMock } from '../lib/license_state.mock'; -import { verifyApiAccess } from '../lib/license_api_access'; -import { mockHandlerArguments } from './_mock_handler_arguments'; -import { alertsClientMock } from '../alerts_client.mock'; -import { AlertTypeDisabledError } from '../lib/errors/alert_type_disabled'; -import { AlertNotifyWhenType } from '../../common'; +import { licenseStateMock } from '../../lib/license_state.mock'; +import { verifyApiAccess } from '../../lib/license_api_access'; +import { mockHandlerArguments } from './../_mock_handler_arguments'; +import { alertsClientMock } from '../../alerts_client.mock'; +import { AlertTypeDisabledError } from '../../lib/errors/alert_type_disabled'; +import { AlertNotifyWhenType } from '../../../common'; const alertsClient = alertsClientMock.create(); -jest.mock('../lib/license_api_access.ts', () => ({ +jest.mock('../../lib/license_api_access.ts', () => ({ verifyApiAccess: jest.fn(), })); diff --git a/x-pack/plugins/alerting/server/routes/update.ts b/x-pack/plugins/alerting/server/routes/legacy/update.ts similarity index 81% rename from x-pack/plugins/alerting/server/routes/update.ts rename to x-pack/plugins/alerting/server/routes/legacy/update.ts index 11373e71d14be2..23a0719319e00f 100644 --- a/x-pack/plugins/alerting/server/routes/update.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/update.ts @@ -6,13 +6,17 @@ */ import { schema } from '@kbn/config-schema'; -import type { AlertingRouter } from '../types'; -import { ILicenseState } from '../lib/license_state'; -import { verifyApiAccess } from '../lib/license_api_access'; -import { validateDurationSchema } from '../lib'; -import { handleDisabledApiKeysError } from './lib/error_handler'; -import { AlertNotifyWhenType, BASE_ALERT_API_PATH, validateNotifyWhenType } from '../../common'; -import { AlertTypeDisabledError } from '../lib/errors/alert_type_disabled'; +import type { AlertingRouter } from '../../types'; +import { ILicenseState } from '../../lib/license_state'; +import { verifyApiAccess } from '../../lib/license_api_access'; +import { validateDurationSchema } from '../../lib'; +import { handleDisabledApiKeysError } from './../lib/error_handler'; +import { AlertTypeDisabledError } from '../../lib/errors/alert_type_disabled'; +import { + AlertNotifyWhenType, + LEGACY_BASE_ALERT_API_PATH, + validateNotifyWhenType, +} from '../../../common'; const paramSchema = schema.object({ id: schema.string(), @@ -41,7 +45,7 @@ const bodySchema = schema.object({ export const updateAlertRoute = (router: AlertingRouter, licenseState: ILicenseState) => { router.put( { - path: `${BASE_ALERT_API_PATH}/alert/{id}`, + path: `${LEGACY_BASE_ALERT_API_PATH}/alert/{id}`, validate: { body: bodySchema, params: paramSchema, diff --git a/x-pack/plugins/alerting/server/routes/update_api_key.test.ts b/x-pack/plugins/alerting/server/routes/legacy/update_api_key.test.ts similarity index 86% rename from x-pack/plugins/alerting/server/routes/update_api_key.test.ts rename to x-pack/plugins/alerting/server/routes/legacy/update_api_key.test.ts index 0added369fd618..3f556f480f69c6 100644 --- a/x-pack/plugins/alerting/server/routes/update_api_key.test.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/update_api_key.test.ts @@ -7,13 +7,13 @@ import { updateApiKeyRoute } from './update_api_key'; import { httpServiceMock } from 'src/core/server/mocks'; -import { licenseStateMock } from '../lib/license_state.mock'; -import { mockHandlerArguments } from './_mock_handler_arguments'; -import { alertsClientMock } from '../alerts_client.mock'; -import { AlertTypeDisabledError } from '../lib/errors/alert_type_disabled'; +import { licenseStateMock } from '../../lib/license_state.mock'; +import { mockHandlerArguments } from './../_mock_handler_arguments'; +import { alertsClientMock } from '../../alerts_client.mock'; +import { AlertTypeDisabledError } from '../../lib/errors/alert_type_disabled'; const alertsClient = alertsClientMock.create(); -jest.mock('../lib/license_api_access.ts', () => ({ +jest.mock('../../lib/license_api_access.ts', () => ({ verifyApiAccess: jest.fn(), })); diff --git a/x-pack/plugins/alerting/server/routes/update_api_key.ts b/x-pack/plugins/alerting/server/routes/legacy/update_api_key.ts similarity index 72% rename from x-pack/plugins/alerting/server/routes/update_api_key.ts rename to x-pack/plugins/alerting/server/routes/legacy/update_api_key.ts index a615ee8a5bbd2a..a4da2538b0bf97 100644 --- a/x-pack/plugins/alerting/server/routes/update_api_key.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/update_api_key.ts @@ -6,12 +6,12 @@ */ import { schema } from '@kbn/config-schema'; -import type { AlertingRouter } from '../types'; -import { ILicenseState } from '../lib/license_state'; -import { verifyApiAccess } from '../lib/license_api_access'; -import { BASE_ALERT_API_PATH } from '../../common'; -import { handleDisabledApiKeysError } from './lib/error_handler'; -import { AlertTypeDisabledError } from '../lib/errors/alert_type_disabled'; +import type { AlertingRouter } from '../../types'; +import { ILicenseState } from '../../lib/license_state'; +import { verifyApiAccess } from '../../lib/license_api_access'; +import { LEGACY_BASE_ALERT_API_PATH } from '../../../common'; +import { handleDisabledApiKeysError } from './../lib/error_handler'; +import { AlertTypeDisabledError } from '../../lib/errors/alert_type_disabled'; const paramSchema = schema.object({ id: schema.string(), @@ -20,7 +20,7 @@ const paramSchema = schema.object({ export const updateApiKeyRoute = (router: AlertingRouter, licenseState: ILicenseState) => { router.post( { - path: `${BASE_ALERT_API_PATH}/alert/{id}/_update_api_key`, + path: `${LEGACY_BASE_ALERT_API_PATH}/alert/{id}/_update_api_key`, validate: { params: paramSchema, }, diff --git a/x-pack/plugins/alerting/server/routes/lib/index.ts b/x-pack/plugins/alerting/server/routes/lib/index.ts new file mode 100644 index 00000000000000..142513e23e5e7f --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/lib/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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { + handleDisabledApiKeysError, + isApiKeyDisabledError, + isSecurityPluginDisabledError, +} from './error_handler'; +export { renameKeys } from './rename_keys'; +export { AsApiContract, RewriteRequestCase, RewriteResponseCase } from './rewrite_request_case'; +export { verifyAccessAndContext } from './verify_access_and_context'; diff --git a/x-pack/plugins/alerting/server/routes/lib/rewrite_request_case.ts b/x-pack/plugins/alerting/server/routes/lib/rewrite_request_case.ts new file mode 100644 index 00000000000000..361ba5ff5e55de --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/lib/rewrite_request_case.ts @@ -0,0 +1,82 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { JsonValue } from '../../../../../../src/plugins/kibana_utils/common'; + +type RenameAlertToRule = K extends `alertTypeId` + ? `ruleTypeId` + : K extends `alertId` + ? `ruleId` + : K extends `alertExecutionStatus` + ? `ruleExecutionStatus` + : K extends `actionTypeId` + ? `connectorTypeId` + : K extends `alertInstanceId` + ? `alertId` + : K extends `mutedInstanceIds` + ? `mutedAlertIds` + : K extends `instances` + ? `alerts` + : K; + +export type AsApiContract< + T, + ComplexPropertyKeys = `actions` | `executionStatus`, + OpaquePropertyKeys = `params` +> = T extends Array + ? Array> + : { + [K in keyof T as CamelToSnake< + RenameAlertToRule> + >]: K extends OpaquePropertyKeys + ? // don't convert explciitly opaque types which we treat as a black box + T[K] + : T[K] extends undefined + ? AsApiContract> | undefined + : // don't convert built in types + T[K] extends Date | JsonValue + ? T[K] + : T[K] extends Array + ? Array> + : K extends ComplexPropertyKeys + ? AsApiContract + : T[K] extends object + ? AsApiContract + : // don't convert anything else + T[K]; + }; + +export type RewriteRequestCase = (requested: AsApiContract) => T; +export type RewriteResponseCase = ( + responded: T +) => T extends Array ? Array> : AsApiContract; + +/** + * This type maps Camel Case strings into their Snake Case version. + * This is achieved by checking each character and, if it is an uppercase character, it is mapped to an + * underscore followed by a lowercase one. + * + * The reason there are two ternaries is that, for perfformance reasons, TS limits its + * character parsing to ~15 characters. + * To get around this we use the second turnery to parse 2 characters at a time, which allows us to support + * strings that are 30 characters long. + * + * If you get the TS #2589 error ("Type instantiation is excessively deep and possibly infinite") then most + * likely you have a string that's longer than 30 characters. + * Address this by reducing the length if possible, otherwise, you'll need to add a 3rd ternary which + * parses 3 chars at a time :grimace: + * + * For more details see this PR comment: https://github.com/microsoft/TypeScript/pull/40336#issuecomment-686723087 + */ +type CamelToSnake = string extends T + ? string + : T extends `${infer C0}${infer C1}${infer R}` + ? `${C0 extends Uppercase ? '_' : ''}${Lowercase}${C1 extends Uppercase + ? '_' + : ''}${Lowercase}${CamelToSnake}` + : T extends `${infer C0}${infer R}` + ? `${C0 extends Uppercase ? '_' : ''}${Lowercase}${CamelToSnake}` + : ''; diff --git a/x-pack/plugins/alerting/server/routes/lib/verify_access_and_context.ts b/x-pack/plugins/alerting/server/routes/lib/verify_access_and_context.ts new file mode 100644 index 00000000000000..f0177f04bf9b27 --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/lib/verify_access_and_context.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { RequestHandler } from 'kibana/server'; +import { ILicenseState, isErrorThatHandlesItsOwnResponse, verifyApiAccess } from '../../lib'; +import { AlertingRequestHandlerContext } from '../../types'; + +type AlertingRequestHandlerWrapper = ( + licenseState: ILicenseState, + handler: RequestHandler +) => RequestHandler; + +export const verifyAccessAndContext: AlertingRequestHandlerWrapper = (licenseState, handler) => { + return async (context, request, response) => { + verifyApiAccess(licenseState); + + if (!context.alerting) { + return response.badRequest({ body: 'RouteHandlerContext is not registered for alerting' }); + } + + try { + return await handler(context, request, response); + } catch (e) { + if (isErrorThatHandlesItsOwnResponse(e)) { + return e.sendResponse(response); + } + throw e; + } + }; +}; diff --git a/x-pack/plugins/alerting/server/routes/mute_alert.test.ts b/x-pack/plugins/alerting/server/routes/mute_alert.test.ts new file mode 100644 index 00000000000000..64ba22f2980ecb --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/mute_alert.test.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { muteAlertRoute } from './mute_alert'; +import { httpServiceMock } from 'src/core/server/mocks'; +import { licenseStateMock } from '../lib/license_state.mock'; +import { mockHandlerArguments } from './_mock_handler_arguments'; +import { alertsClientMock } from '../alerts_client.mock'; +import { AlertTypeDisabledError } from '../lib/errors/alert_type_disabled'; + +const alertsClient = alertsClientMock.create(); +jest.mock('../lib/license_api_access.ts', () => ({ + verifyApiAccess: jest.fn(), +})); + +beforeEach(() => { + jest.resetAllMocks(); +}); + +describe('muteAlertRoute', () => { + it('mutes an alert', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + muteAlertRoute(router, licenseState); + + const [config, handler] = router.post.mock.calls[0]; + + expect(config.path).toMatchInlineSnapshot( + `"/api/alerting/rule/{rule_id}/alert/{alert_id}/_mute"` + ); + + alertsClient.muteInstance.mockResolvedValueOnce(); + + const [context, req, res] = mockHandlerArguments( + { alertsClient }, + { + params: { + rule_id: '1', + alert_id: '2', + }, + }, + ['noContent'] + ); + + expect(await handler(context, req, res)).toEqual(undefined); + + expect(alertsClient.muteInstance).toHaveBeenCalledTimes(1); + expect(alertsClient.muteInstance.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "alertId": "1", + "alertInstanceId": "2", + }, + ] + `); + + expect(res.noContent).toHaveBeenCalled(); + }); + + it('ensures the rule type gets validated for the license', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + muteAlertRoute(router, licenseState); + + const [, handler] = router.post.mock.calls[0]; + + alertsClient.muteInstance.mockRejectedValue( + new AlertTypeDisabledError('Fail', 'license_invalid') + ); + + const [context, req, res] = mockHandlerArguments({ alertsClient }, { params: {}, body: {} }, [ + 'ok', + 'forbidden', + ]); + + await handler(context, req, res); + + expect(res.forbidden).toHaveBeenCalledWith({ body: { message: 'Fail' } }); + }); +}); diff --git a/x-pack/plugins/alerting/server/routes/mute_alert.ts b/x-pack/plugins/alerting/server/routes/mute_alert.ts new file mode 100644 index 00000000000000..f1b928cf8c543b --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/mute_alert.ts @@ -0,0 +1,55 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { IRouter } from 'kibana/server'; +import { schema } from '@kbn/config-schema'; +import { ILicenseState, AlertTypeDisabledError } from '../lib'; +import { MuteOptions } from '../alerts_client'; +import { RewriteRequestCase, verifyAccessAndContext } from './lib'; +import { AlertingRequestHandlerContext, BASE_ALERTING_API_PATH } from '../types'; + +const paramSchema = schema.object({ + rule_id: schema.string(), + alert_id: schema.string(), +}); + +const rewriteParamsReq: RewriteRequestCase = ({ + rule_id: alertId, + alert_id: alertInstanceId, +}) => ({ + alertId, + alertInstanceId, +}); + +export const muteAlertRoute = ( + router: IRouter, + licenseState: ILicenseState +) => { + router.post( + { + path: `${BASE_ALERTING_API_PATH}/rule/{rule_id}/alert/{alert_id}/_mute`, + validate: { + params: paramSchema, + }, + }, + router.handleLegacyErrors( + verifyAccessAndContext(licenseState, async function (context, req, res) { + const alertsClient = context.alerting.getAlertsClient(); + const params = rewriteParamsReq(req.params); + try { + await alertsClient.muteInstance(params); + return res.noContent(); + } catch (e) { + if (e instanceof AlertTypeDisabledError) { + return e.sendResponse(res); + } + throw e; + } + }) + ) + ); +}; diff --git a/x-pack/plugins/alerting/server/routes/mute_all_rule.test.ts b/x-pack/plugins/alerting/server/routes/mute_all_rule.test.ts new file mode 100644 index 00000000000000..0d53708db2567d --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/mute_all_rule.test.ts @@ -0,0 +1,80 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { muteAllRuleRoute } from './mute_all_rule'; +import { httpServiceMock } from 'src/core/server/mocks'; +import { licenseStateMock } from '../lib/license_state.mock'; +import { mockHandlerArguments } from './_mock_handler_arguments'; +import { alertsClientMock } from '../alerts_client.mock'; +import { AlertTypeDisabledError } from '../lib/errors/alert_type_disabled'; + +const alertsClient = alertsClientMock.create(); +jest.mock('../lib/license_api_access.ts', () => ({ + verifyApiAccess: jest.fn(), +})); + +beforeEach(() => { + jest.resetAllMocks(); +}); + +describe('muteAllRuleRoute', () => { + it('mute a rule', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + muteAllRuleRoute(router, licenseState); + + const [config, handler] = router.post.mock.calls[0]; + + expect(config.path).toMatchInlineSnapshot(`"/api/alerting/rule/{id}/_mute_all"`); + + alertsClient.muteAll.mockResolvedValueOnce(); + + const [context, req, res] = mockHandlerArguments( + { alertsClient }, + { + params: { + id: '1', + }, + }, + ['noContent'] + ); + + expect(await handler(context, req, res)).toEqual(undefined); + + expect(alertsClient.muteAll).toHaveBeenCalledTimes(1); + expect(alertsClient.muteAll.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "id": "1", + }, + ] + `); + + expect(res.noContent).toHaveBeenCalled(); + }); + + it('ensures the rule type gets validated for the license', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + muteAllRuleRoute(router, licenseState); + + const [, handler] = router.post.mock.calls[0]; + + alertsClient.muteAll.mockRejectedValue(new AlertTypeDisabledError('Fail', 'license_invalid')); + + const [context, req, res] = mockHandlerArguments({ alertsClient }, { params: {}, body: {} }, [ + 'ok', + 'forbidden', + ]); + + await handler(context, req, res); + + expect(res.forbidden).toHaveBeenCalledWith({ body: { message: 'Fail' } }); + }); +}); diff --git a/x-pack/plugins/alerting/server/routes/mute_all_rule.ts b/x-pack/plugins/alerting/server/routes/mute_all_rule.ts new file mode 100644 index 00000000000000..29d40249ef0797 --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/mute_all_rule.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { IRouter } from 'kibana/server'; +import { schema } from '@kbn/config-schema'; +import { ILicenseState, AlertTypeDisabledError } from '../lib'; +import { verifyAccessAndContext } from './lib'; +import { AlertingRequestHandlerContext, BASE_ALERTING_API_PATH } from '../types'; + +const paramSchema = schema.object({ + id: schema.string(), +}); + +export const muteAllRuleRoute = ( + router: IRouter, + licenseState: ILicenseState +) => { + router.post( + { + path: `${BASE_ALERTING_API_PATH}/rule/{id}/_mute_all`, + validate: { + params: paramSchema, + }, + }, + router.handleLegacyErrors( + verifyAccessAndContext(licenseState, async function (context, req, res) { + const alertsClient = context.alerting.getAlertsClient(); + const { id } = req.params; + try { + await alertsClient.muteAll({ id }); + return res.noContent(); + } catch (e) { + if (e instanceof AlertTypeDisabledError) { + return e.sendResponse(res); + } + throw e; + } + }) + ) + ); +}; diff --git a/x-pack/plugins/alerting/server/routes/rule_types.test.ts b/x-pack/plugins/alerting/server/routes/rule_types.test.ts new file mode 100644 index 00000000000000..58c9a4b4c46fd0 --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/rule_types.test.ts @@ -0,0 +1,223 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ruleTypesRoute } from './rule_types'; +import { httpServiceMock } from 'src/core/server/mocks'; +import { licenseStateMock } from '../lib/license_state.mock'; +import { verifyApiAccess } from '../lib/license_api_access'; +import { mockHandlerArguments } from './_mock_handler_arguments'; +import { alertsClientMock } from '../alerts_client.mock'; +import { RecoveredActionGroup } from '../../common'; +import { RegistryAlertTypeWithAuth } from '../authorization'; +import { AsApiContract } from './lib'; + +const alertsClient = alertsClientMock.create(); + +jest.mock('../lib/license_api_access.ts', () => ({ + verifyApiAccess: jest.fn(), +})); + +beforeEach(() => { + jest.resetAllMocks(); +}); + +describe('ruleTypesRoute', () => { + it('lists rule types with proper parameters', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + ruleTypesRoute(router, licenseState); + + const [config, handler] = router.get.mock.calls[0]; + + expect(config.path).toMatchInlineSnapshot(`"/api/alerting/rule_types"`); + + const listTypes = [ + { + id: '1', + name: 'name', + actionGroups: [ + { + id: 'default', + name: 'Default', + }, + ], + defaultActionGroupId: 'default', + minimumLicenseRequired: 'basic', + recoveryActionGroup: RecoveredActionGroup, + authorizedConsumers: {}, + actionVariables: { + context: [], + state: [], + }, + producer: 'test', + enabledInLicense: true, + } as RegistryAlertTypeWithAuth, + ]; + const expectedResult: Array> = [ + { + id: '1', + name: 'name', + action_groups: [ + { + id: 'default', + name: 'Default', + }, + ], + default_action_group_id: 'default', + minimum_license_required: 'basic', + recovery_action_group: RecoveredActionGroup, + authorized_consumers: {}, + action_variables: { + context: [], + state: [], + }, + producer: 'test', + enabled_in_license: true, + }, + ]; + alertsClient.listAlertTypes.mockResolvedValueOnce(new Set(listTypes)); + + const [context, req, res] = mockHandlerArguments({ alertsClient }, {}, ['ok']); + + expect(await handler(context, req, res)).toMatchInlineSnapshot(` + Object { + "body": Array [ + Object { + "action_groups": Array [ + Object { + "id": "default", + "name": "Default", + }, + ], + "action_variables": Object { + "context": Array [], + "state": Array [], + }, + "authorized_consumers": Object {}, + "default_action_group_id": "default", + "enabled_in_license": true, + "id": "1", + "minimum_license_required": "basic", + "name": "name", + "producer": "test", + "recovery_action_group": Object { + "id": "recovered", + "name": "Recovered", + }, + }, + ], + } + `); + + expect(alertsClient.listAlertTypes).toHaveBeenCalledTimes(1); + + expect(res.ok).toHaveBeenCalledWith({ + body: expectedResult, + }); + }); + + it('ensures the license allows listing rule types', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + ruleTypesRoute(router, licenseState); + + const [config, handler] = router.get.mock.calls[0]; + + expect(config.path).toMatchInlineSnapshot(`"/api/alerting/rule_types"`); + + const listTypes = [ + { + id: '1', + name: 'name', + actionGroups: [ + { + id: 'default', + name: 'Default', + }, + ], + defaultActionGroupId: 'default', + minimumLicenseRequired: 'basic', + recoveryActionGroup: RecoveredActionGroup, + authorizedConsumers: {}, + actionVariables: { + context: [], + state: [], + }, + producer: 'alerts', + enabledInLicense: true, + } as RegistryAlertTypeWithAuth, + ]; + + alertsClient.listAlertTypes.mockResolvedValueOnce(new Set(listTypes)); + + const [context, req, res] = mockHandlerArguments( + { alertsClient }, + { + params: { id: '1' }, + }, + ['ok'] + ); + + await handler(context, req, res); + + expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); + }); + + it('ensures the license check prevents listing rule types', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + (verifyApiAccess as jest.Mock).mockImplementation(() => { + throw new Error('OMG'); + }); + + ruleTypesRoute(router, licenseState); + + const [config, handler] = router.get.mock.calls[0]; + + expect(config.path).toMatchInlineSnapshot(`"/api/alerting/rule_types"`); + + const listTypes = [ + { + id: '1', + name: 'name', + actionGroups: [ + { + id: 'default', + name: 'Default', + }, + ], + defaultActionGroupId: 'default', + minimumLicenseRequired: 'basic', + recoveryActionGroup: RecoveredActionGroup, + authorizedConsumers: {}, + actionVariables: { + context: [], + state: [], + }, + producer: 'alerts', + enabledInLicense: true, + } as RegistryAlertTypeWithAuth, + ]; + + alertsClient.listAlertTypes.mockResolvedValueOnce(new Set(listTypes)); + + const [context, req, res] = mockHandlerArguments( + { alertsClient }, + { + params: { id: '1' }, + }, + ['ok'] + ); + + expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); + + expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); + }); +}); diff --git a/x-pack/plugins/alerting/server/routes/rule_types.ts b/x-pack/plugins/alerting/server/routes/rule_types.ts new file mode 100644 index 00000000000000..a3a44f9b013cd8 --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/rule_types.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { IRouter } from 'kibana/server'; +import { ILicenseState } from '../lib'; +import { RegistryAlertTypeWithAuth } from '../authorization'; +import { RewriteResponseCase, verifyAccessAndContext } from './lib'; +import { AlertingRequestHandlerContext, BASE_ALERTING_API_PATH } from '../types'; + +const rewriteBodyRes: RewriteResponseCase = (results) => { + return results.map( + ({ + enabledInLicense, + recoveryActionGroup, + actionGroups, + defaultActionGroupId, + minimumLicenseRequired, + actionVariables, + authorizedConsumers, + ...rest + }) => ({ + ...rest, + enabled_in_license: enabledInLicense, + recovery_action_group: recoveryActionGroup, + action_groups: actionGroups, + default_action_group_id: defaultActionGroupId, + minimum_license_required: minimumLicenseRequired, + action_variables: actionVariables, + authorized_consumers: authorizedConsumers, + }) + ); +}; + +export const ruleTypesRoute = ( + router: IRouter, + licenseState: ILicenseState +) => { + router.get( + { + path: `${BASE_ALERTING_API_PATH}/rule_types`, + validate: {}, + }, + router.handleLegacyErrors( + verifyAccessAndContext(licenseState, async function (context, req, res) { + const ruleTypes = Array.from(await context.alerting.getAlertsClient().listAlertTypes()); + return res.ok({ + body: rewriteBodyRes(ruleTypes), + }); + }) + ) + ); +}; diff --git a/x-pack/plugins/alerting/server/routes/unmute_alert.test.ts b/x-pack/plugins/alerting/server/routes/unmute_alert.test.ts new file mode 100644 index 00000000000000..a491ba394f8397 --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/unmute_alert.test.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { unmuteAlertRoute } from './unmute_alert'; +import { httpServiceMock } from 'src/core/server/mocks'; +import { licenseStateMock } from '../lib/license_state.mock'; +import { mockHandlerArguments } from './_mock_handler_arguments'; +import { alertsClientMock } from '../alerts_client.mock'; +import { AlertTypeDisabledError } from '../lib/errors/alert_type_disabled'; + +const alertsClient = alertsClientMock.create(); +jest.mock('../lib/license_api_access.ts', () => ({ + verifyApiAccess: jest.fn(), +})); + +beforeEach(() => { + jest.resetAllMocks(); +}); + +describe('unmuteAlertRoute', () => { + it('unmutes an alert', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + unmuteAlertRoute(router, licenseState); + + const [config, handler] = router.post.mock.calls[0]; + + expect(config.path).toMatchInlineSnapshot( + `"/api/alerting/rule/{rule_id}/alert/{alert_id}/_unmute"` + ); + + alertsClient.unmuteInstance.mockResolvedValueOnce(); + + const [context, req, res] = mockHandlerArguments( + { alertsClient }, + { + params: { + rule_id: '1', + alert_id: '2', + }, + }, + ['noContent'] + ); + + expect(await handler(context, req, res)).toEqual(undefined); + + expect(alertsClient.unmuteInstance).toHaveBeenCalledTimes(1); + expect(alertsClient.unmuteInstance.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "alertId": "1", + "alertInstanceId": "2", + }, + ] + `); + + expect(res.noContent).toHaveBeenCalled(); + }); + + it('ensures the rule type gets validated for the license', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + unmuteAlertRoute(router, licenseState); + + const [, handler] = router.post.mock.calls[0]; + + alertsClient.unmuteInstance.mockRejectedValue( + new AlertTypeDisabledError('Fail', 'license_invalid') + ); + + const [context, req, res] = mockHandlerArguments({ alertsClient }, { params: {}, body: {} }, [ + 'ok', + 'forbidden', + ]); + + await handler(context, req, res); + + expect(res.forbidden).toHaveBeenCalledWith({ body: { message: 'Fail' } }); + }); +}); diff --git a/x-pack/plugins/alerting/server/routes/unmute_alert.ts b/x-pack/plugins/alerting/server/routes/unmute_alert.ts new file mode 100644 index 00000000000000..94bd6cd9af75f3 --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/unmute_alert.ts @@ -0,0 +1,55 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { IRouter } from 'kibana/server'; +import { schema } from '@kbn/config-schema'; +import { ILicenseState, AlertTypeDisabledError } from '../lib'; +import { MuteOptions } from '../alerts_client'; +import { RewriteRequestCase, verifyAccessAndContext } from './lib'; +import { AlertingRequestHandlerContext, BASE_ALERTING_API_PATH } from '../types'; + +const paramSchema = schema.object({ + rule_id: schema.string(), + alert_id: schema.string(), +}); + +const rewriteParamsReq: RewriteRequestCase = ({ + rule_id: alertId, + alert_id: alertInstanceId, +}) => ({ + alertId, + alertInstanceId, +}); + +export const unmuteAlertRoute = ( + router: IRouter, + licenseState: ILicenseState +) => { + router.post( + { + path: `${BASE_ALERTING_API_PATH}/rule/{rule_id}/alert/{alert_id}/_unmute`, + validate: { + params: paramSchema, + }, + }, + router.handleLegacyErrors( + verifyAccessAndContext(licenseState, async function (context, req, res) { + const alertsClient = context.alerting.getAlertsClient(); + const params = rewriteParamsReq(req.params); + try { + await alertsClient.unmuteInstance(params); + return res.noContent(); + } catch (e) { + if (e instanceof AlertTypeDisabledError) { + return e.sendResponse(res); + } + throw e; + } + }) + ) + ); +}; diff --git a/x-pack/plugins/alerting/server/routes/unmute_all_rule.test.ts b/x-pack/plugins/alerting/server/routes/unmute_all_rule.test.ts new file mode 100644 index 00000000000000..f873863bcb9022 --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/unmute_all_rule.test.ts @@ -0,0 +1,80 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { unmuteAllRuleRoute } from './unmute_all_rule'; +import { httpServiceMock } from 'src/core/server/mocks'; +import { licenseStateMock } from '../lib/license_state.mock'; +import { mockHandlerArguments } from './_mock_handler_arguments'; +import { alertsClientMock } from '../alerts_client.mock'; +import { AlertTypeDisabledError } from '../lib/errors/alert_type_disabled'; + +const alertsClient = alertsClientMock.create(); +jest.mock('../lib/license_api_access.ts', () => ({ + verifyApiAccess: jest.fn(), +})); + +beforeEach(() => { + jest.resetAllMocks(); +}); + +describe('unmuteAllRuleRoute', () => { + it('unmutes a rule', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + unmuteAllRuleRoute(router, licenseState); + + const [config, handler] = router.post.mock.calls[0]; + + expect(config.path).toMatchInlineSnapshot(`"/api/alerting/rule/{id}/_unmute_all"`); + + alertsClient.unmuteAll.mockResolvedValueOnce(); + + const [context, req, res] = mockHandlerArguments( + { alertsClient }, + { + params: { + id: '1', + }, + }, + ['noContent'] + ); + + expect(await handler(context, req, res)).toEqual(undefined); + + expect(alertsClient.unmuteAll).toHaveBeenCalledTimes(1); + expect(alertsClient.unmuteAll.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "id": "1", + }, + ] + `); + + expect(res.noContent).toHaveBeenCalled(); + }); + + it('ensures the rule type gets validated for the license', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + unmuteAllRuleRoute(router, licenseState); + + const [, handler] = router.post.mock.calls[0]; + + alertsClient.unmuteAll.mockRejectedValue(new AlertTypeDisabledError('Fail', 'license_invalid')); + + const [context, req, res] = mockHandlerArguments({ alertsClient }, { params: {}, body: {} }, [ + 'ok', + 'forbidden', + ]); + + await handler(context, req, res); + + expect(res.forbidden).toHaveBeenCalledWith({ body: { message: 'Fail' } }); + }); +}); diff --git a/x-pack/plugins/alerting/server/routes/unmute_all_rule.ts b/x-pack/plugins/alerting/server/routes/unmute_all_rule.ts new file mode 100644 index 00000000000000..96176e916cd7c8 --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/unmute_all_rule.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { IRouter } from 'kibana/server'; +import { schema } from '@kbn/config-schema'; +import { ILicenseState, AlertTypeDisabledError } from '../lib'; +import { verifyAccessAndContext } from './lib'; +import { AlertingRequestHandlerContext, BASE_ALERTING_API_PATH } from '../types'; + +const paramSchema = schema.object({ + id: schema.string(), +}); + +export const unmuteAllRuleRoute = ( + router: IRouter, + licenseState: ILicenseState +) => { + router.post( + { + path: `${BASE_ALERTING_API_PATH}/rule/{id}/_unmute_all`, + validate: { + params: paramSchema, + }, + }, + router.handleLegacyErrors( + verifyAccessAndContext(licenseState, async function (context, req, res) { + const alertsClient = context.alerting.getAlertsClient(); + const { id } = req.params; + try { + await alertsClient.unmuteAll({ id }); + return res.noContent(); + } catch (e) { + if (e instanceof AlertTypeDisabledError) { + return e.sendResponse(res); + } + throw e; + } + }) + ) + ); +}; diff --git a/x-pack/plugins/alerting/server/routes/update_rule.test.ts b/x-pack/plugins/alerting/server/routes/update_rule.test.ts new file mode 100644 index 00000000000000..a7121214cd3d34 --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/update_rule.test.ts @@ -0,0 +1,215 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { pick } from 'lodash'; +import { updateRuleRoute } from './update_rule'; +import { httpServiceMock } from 'src/core/server/mocks'; +import { licenseStateMock } from '../lib/license_state.mock'; +import { verifyApiAccess } from '../lib/license_api_access'; +import { mockHandlerArguments } from './_mock_handler_arguments'; +import { UpdateOptions } from '../alerts_client'; +import { alertsClientMock } from '../alerts_client.mock'; +import { AlertTypeDisabledError } from '../lib/errors/alert_type_disabled'; +import { AlertNotifyWhenType } from '../../common'; +import { AsApiContract } from './lib'; +import { PartialAlert } from '../types'; + +const alertsClient = alertsClientMock.create(); +jest.mock('../lib/license_api_access.ts', () => ({ + verifyApiAccess: jest.fn(), +})); + +beforeEach(() => { + jest.resetAllMocks(); +}); + +describe('updateRuleRoute', () => { + const mockedAlert = { + id: '1', + name: 'abc', + alertTypeId: '1', + tags: ['foo'], + throttle: '10m', + schedule: { interval: '12s' }, + params: { + otherField: false, + }, + createdAt: new Date(), + updatedAt: new Date(), + actions: [ + { + group: 'default', + id: '2', + actionTypeId: 'test', + params: { + baz: true, + }, + }, + ], + notifyWhen: 'onActionGroupChange' as AlertNotifyWhenType, + }; + + const updateRequest: AsApiContract['data']> = { + ...pick(mockedAlert, 'name', 'tags', 'schedule', 'params', 'throttle'), + notify_when: mockedAlert.notifyWhen, + actions: [ + { + group: mockedAlert.actions[0].group, + id: mockedAlert.actions[0].id, + params: mockedAlert.actions[0].params, + }, + ], + }; + + const updateResult: AsApiContract> = { + ...updateRequest, + id: mockedAlert.id, + updated_at: mockedAlert.updatedAt, + created_at: mockedAlert.createdAt, + rule_type_id: mockedAlert.alertTypeId, + actions: mockedAlert.actions.map(({ actionTypeId, ...rest }) => ({ + ...rest, + connector_type_id: actionTypeId, + })), + }; + + it('updates a rule with proper parameters', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + updateRuleRoute(router, licenseState); + + const [config, handler] = router.put.mock.calls[0]; + + expect(config.path).toMatchInlineSnapshot(`"/api/alerting/rule/{id}"`); + + alertsClient.update.mockResolvedValueOnce(mockedAlert); + + const [context, req, res] = mockHandlerArguments( + { alertsClient }, + { + params: { + id: '1', + }, + body: updateRequest, + }, + ['ok'] + ); + + expect(await handler(context, req, res)).toEqual({ body: updateResult }); + + expect(alertsClient.update).toHaveBeenCalledTimes(1); + expect(alertsClient.update.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "data": Object { + "actions": Array [ + Object { + "group": "default", + "id": "2", + "params": Object { + "baz": true, + }, + }, + ], + "name": "abc", + "notifyWhen": "onActionGroupChange", + "params": Object { + "otherField": false, + }, + "schedule": Object { + "interval": "12s", + }, + "tags": Array [ + "foo", + ], + "throttle": "10m", + }, + "id": "1", + }, + ] + `); + + expect(res.ok).toHaveBeenCalled(); + }); + + it('ensures the license allows updating rules', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + updateRuleRoute(router, licenseState); + + const [, handler] = router.put.mock.calls[0]; + + alertsClient.update.mockResolvedValueOnce(mockedAlert); + + const [context, req, res] = mockHandlerArguments( + { alertsClient }, + { + params: { + id: '1', + }, + body: updateRequest, + }, + ['ok'] + ); + + await handler(context, req, res); + + expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); + }); + + it('ensures the license check prevents updating rules', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + (verifyApiAccess as jest.Mock).mockImplementation(() => { + throw new Error('OMG'); + }); + + updateRuleRoute(router, licenseState); + + const [, handler] = router.put.mock.calls[0]; + + alertsClient.update.mockResolvedValueOnce(mockedAlert); + + const [context, req, res] = mockHandlerArguments( + { alertsClient }, + { + params: { + id: '1', + }, + body: updateRequest, + }, + ['ok'] + ); + + expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); + + expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); + }); + + it('ensures the rule type gets validated for the license', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + updateRuleRoute(router, licenseState); + + const [, handler] = router.put.mock.calls[0]; + + alertsClient.update.mockRejectedValue(new AlertTypeDisabledError('Fail', 'license_invalid')); + + const [context, req, res] = mockHandlerArguments({ alertsClient }, { params: {}, body: {} }, [ + 'ok', + 'forbidden', + ]); + + await handler(context, req, res); + + expect(res.forbidden).toHaveBeenCalledWith({ body: { message: 'Fail' } }); + }); +}); diff --git a/x-pack/plugins/alerting/server/routes/update_rule.ts b/x-pack/plugins/alerting/server/routes/update_rule.ts new file mode 100644 index 00000000000000..ef5bd005587529 --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/update_rule.ts @@ -0,0 +1,147 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { schema } from '@kbn/config-schema'; +import { IRouter } from 'kibana/server'; +import { ILicenseState, AlertTypeDisabledError, validateDurationSchema } from '../lib'; +import { AlertNotifyWhenType } from '../../common'; +import { UpdateOptions } from '../alerts_client'; +import { + verifyAccessAndContext, + RewriteResponseCase, + RewriteRequestCase, + handleDisabledApiKeysError, +} from './lib'; +import { + AlertTypeParams, + AlertingRequestHandlerContext, + BASE_ALERTING_API_PATH, + validateNotifyWhenType, + PartialAlert, +} from '../types'; + +const paramSchema = schema.object({ + id: schema.string(), +}); + +const bodySchema = schema.object({ + name: schema.string(), + tags: schema.arrayOf(schema.string(), { defaultValue: [] }), + schedule: schema.object({ + interval: schema.string({ validate: validateDurationSchema }), + }), + throttle: schema.nullable(schema.string({ validate: validateDurationSchema })), + params: schema.recordOf(schema.string(), schema.any(), { defaultValue: {} }), + actions: schema.arrayOf( + schema.object({ + group: schema.string(), + id: schema.string(), + params: schema.recordOf(schema.string(), schema.any(), { defaultValue: {} }), + }), + { defaultValue: [] } + ), + notify_when: schema.string({ validate: validateNotifyWhenType }), +}); + +const rewriteBodyReq: RewriteRequestCase> = (result) => { + const { notify_when: notifyWhen, ...rest } = result.data; + return { + ...result, + data: { + ...rest, + notifyWhen, + }, + }; +}; +const rewriteBodyRes: RewriteResponseCase> = ({ + actions, + alertTypeId, + scheduledTaskId, + createdBy, + updatedBy, + createdAt, + updatedAt, + apiKeyOwner, + notifyWhen, + muteAll, + mutedInstanceIds, + executionStatus, + ...rest +}) => ({ + ...rest, + api_key_owner: apiKeyOwner, + created_by: createdBy, + updated_by: updatedBy, + ...(alertTypeId ? { rule_type_id: alertTypeId } : {}), + ...(scheduledTaskId ? { scheduled_task_id: scheduledTaskId } : {}), + ...(createdAt ? { created_at: createdAt } : {}), + ...(updatedAt ? { updated_at: updatedAt } : {}), + ...(notifyWhen ? { notify_when: notifyWhen } : {}), + ...(muteAll !== undefined ? { mute_all: muteAll } : {}), + ...(mutedInstanceIds ? { muted_alert_ids: mutedInstanceIds } : {}), + ...(executionStatus + ? { + execution_status: { + status: executionStatus.status, + last_execution_date: executionStatus.lastExecutionDate, + }, + } + : {}), + ...(actions + ? { + actions: actions.map(({ group, id, actionTypeId, params }) => ({ + group, + id, + params, + connector_type_id: actionTypeId, + })), + } + : {}), +}); + +export const updateRuleRoute = ( + router: IRouter, + licenseState: ILicenseState +) => { + router.put( + { + path: `${BASE_ALERTING_API_PATH}/rule/{id}`, + validate: { + body: bodySchema, + params: paramSchema, + }, + }, + handleDisabledApiKeysError( + router.handleLegacyErrors( + verifyAccessAndContext(licenseState, async function (context, req, res) { + const alertsClient = context.alerting.getAlertsClient(); + const { id } = req.params; + const rule = req.body; + try { + const alertRes = await alertsClient.update( + rewriteBodyReq({ + id, + data: { + ...rule, + notify_when: rule.notify_when as AlertNotifyWhenType, + }, + }) + ); + return res.ok({ + body: rewriteBodyRes(alertRes), + }); + } catch (e) { + if (e instanceof AlertTypeDisabledError) { + return e.sendResponse(res); + } + throw e; + } + }) + ) + ) + ); +}; diff --git a/x-pack/plugins/alerting/server/routes/update_rule_api_key.test.ts b/x-pack/plugins/alerting/server/routes/update_rule_api_key.test.ts new file mode 100644 index 00000000000000..ced335136adb15 --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/update_rule_api_key.test.ts @@ -0,0 +1,82 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { updateRuleApiKeyRoute } from './update_rule_api_key'; +import { httpServiceMock } from 'src/core/server/mocks'; +import { licenseStateMock } from '../lib/license_state.mock'; +import { mockHandlerArguments } from './_mock_handler_arguments'; +import { alertsClientMock } from '../alerts_client.mock'; +import { AlertTypeDisabledError } from '../lib/errors/alert_type_disabled'; + +const alertsClient = alertsClientMock.create(); +jest.mock('../lib/license_api_access.ts', () => ({ + verifyApiAccess: jest.fn(), +})); + +beforeEach(() => { + jest.resetAllMocks(); +}); + +describe('updateRuleApiKeyRoute', () => { + it('updates api key for a rule', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + updateRuleApiKeyRoute(router, licenseState); + + const [config, handler] = router.post.mock.calls[0]; + + expect(config.path).toMatchInlineSnapshot(`"/internal/alerting/rule/{id}/_update_api_key"`); + + alertsClient.updateApiKey.mockResolvedValueOnce(); + + const [context, req, res] = mockHandlerArguments( + { alertsClient }, + { + params: { + id: '1', + }, + }, + ['noContent'] + ); + + expect(await handler(context, req, res)).toEqual(undefined); + + expect(alertsClient.updateApiKey).toHaveBeenCalledTimes(1); + expect(alertsClient.updateApiKey.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "id": "1", + }, + ] + `); + + expect(res.noContent).toHaveBeenCalled(); + }); + + it('ensures the rule type gets validated for the license', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + updateRuleApiKeyRoute(router, licenseState); + + const [, handler] = router.post.mock.calls[0]; + + alertsClient.updateApiKey.mockRejectedValue( + new AlertTypeDisabledError('Fail', 'license_invalid') + ); + + const [context, req, res] = mockHandlerArguments({ alertsClient }, { params: {}, body: {} }, [ + 'ok', + 'forbidden', + ]); + + await handler(context, req, res); + + expect(res.forbidden).toHaveBeenCalledWith({ body: { message: 'Fail' } }); + }); +}); diff --git a/x-pack/plugins/alerting/server/routes/update_rule_api_key.ts b/x-pack/plugins/alerting/server/routes/update_rule_api_key.ts new file mode 100644 index 00000000000000..57206c68d448d1 --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/update_rule_api_key.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { IRouter } from 'kibana/server'; +import { schema } from '@kbn/config-schema'; +import { ILicenseState, AlertTypeDisabledError } from '../lib'; +import { verifyAccessAndContext } from './lib'; +import { AlertingRequestHandlerContext, INTERNAL_BASE_ALERTING_API_PATH } from '../types'; + +const paramSchema = schema.object({ + id: schema.string(), +}); + +export const updateRuleApiKeyRoute = ( + router: IRouter, + licenseState: ILicenseState +) => { + router.post( + { + path: `${INTERNAL_BASE_ALERTING_API_PATH}/rule/{id}/_update_api_key`, + validate: { + params: paramSchema, + }, + }, + router.handleLegacyErrors( + verifyAccessAndContext(licenseState, async function (context, req, res) { + const alertsClient = context.alerting.getAlertsClient(); + const { id } = req.params; + try { + await alertsClient.updateApiKey({ id }); + return res.noContent(); + } catch (e) { + if (e instanceof AlertTypeDisabledError) { + return e.sendResponse(res); + } + throw e; + } + }) + ) + ); +}; diff --git a/x-pack/plugins/monitoring/public/alerts/configuration.tsx b/x-pack/plugins/monitoring/public/alerts/configuration.tsx index 59411d431d83aa..5416095671d718 100644 --- a/x-pack/plugins/monitoring/public/alerts/configuration.tsx +++ b/x-pack/plugins/monitoring/public/alerts/configuration.tsx @@ -12,7 +12,7 @@ import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiSwitch } from '@elastic/eui'; import { CommonAlert } from '../../common/types/alerts'; import { Legacy } from '../legacy_shims'; import { hideBottomBar, showBottomBar } from '../lib/setup_mode'; -import { BASE_ALERT_API_PATH } from '../../../alerting/common'; +import { LEGACY_BASE_ALERT_API_PATH } from '../../../alerting/common'; interface Props { alert: CommonAlert; @@ -28,7 +28,7 @@ export const AlertConfiguration: React.FC = (props: Props) => { async function disableAlert() { setIsSaving(true); try { - await Legacy.shims.http.post(`${BASE_ALERT_API_PATH}/alert/${alert.id}/_disable`); + await Legacy.shims.http.post(`${LEGACY_BASE_ALERT_API_PATH}/alert/${alert.id}/_disable`); } catch (err) { Legacy.shims.toastNotifications.addDanger({ title: i18n.translate('xpack.monitoring.alerts.panel.disableAlert.errorTitle', { @@ -42,7 +42,7 @@ export const AlertConfiguration: React.FC = (props: Props) => { async function enableAlert() { setIsSaving(true); try { - await Legacy.shims.http.post(`${BASE_ALERT_API_PATH}/alert/${alert.id}/_enable`); + await Legacy.shims.http.post(`${LEGACY_BASE_ALERT_API_PATH}/alert/${alert.id}/_enable`); } catch (err) { Legacy.shims.toastNotifications.addDanger({ title: i18n.translate('xpack.monitoring.alerts.panel.enableAlert.errorTitle', { @@ -56,7 +56,7 @@ export const AlertConfiguration: React.FC = (props: Props) => { async function muteAlert() { setIsSaving(true); try { - await Legacy.shims.http.post(`${BASE_ALERT_API_PATH}/alert/${alert.id}/_mute_all`); + await Legacy.shims.http.post(`${LEGACY_BASE_ALERT_API_PATH}/alert/${alert.id}/_mute_all`); } catch (err) { Legacy.shims.toastNotifications.addDanger({ title: i18n.translate('xpack.monitoring.alerts.panel.muteAlert.errorTitle', { @@ -70,7 +70,7 @@ export const AlertConfiguration: React.FC = (props: Props) => { async function unmuteAlert() { setIsSaving(true); try { - await Legacy.shims.http.post(`${BASE_ALERT_API_PATH}/alert/${alert.id}/_unmute_all`); + await Legacy.shims.http.post(`${LEGACY_BASE_ALERT_API_PATH}/alert/${alert.id}/_unmute_all`); } catch (err) { Legacy.shims.toastNotifications.addDanger({ title: i18n.translate('xpack.monitoring.alerts.panel.ummuteAlert.errorTitle', { diff --git a/x-pack/plugins/monitoring/server/alerts/base_alert.ts b/x-pack/plugins/monitoring/server/alerts/base_alert.ts index fbe487f240699e..bb80d84210a481 100644 --- a/x-pack/plugins/monitoring/server/alerts/base_alert.ts +++ b/x-pack/plugins/monitoring/server/alerts/base_alert.ts @@ -116,7 +116,7 @@ export class BaseAlert { alertsClient: AlertsClient, actionsClient: ActionsClient, actions: AlertEnableAction[] - ): Promise> { + ): Promise> { const existingAlertData = await alertsClient.find({ options: { search: this.alertOptions.id, diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/alerts/enable.ts b/x-pack/plugins/monitoring/server/routes/api/v1/alerts/enable.ts index b452d22506a1c1..01ab392e0563c0 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/alerts/enable.ts +++ b/x-pack/plugins/monitoring/server/routes/api/v1/alerts/enable.ts @@ -13,7 +13,7 @@ import { ALERT_ACTION_TYPE_LOG } from '../../../../../common/constants'; import { ActionResult } from '../../../../../../actions/common'; import { AlertingSecurity } from '../../../../lib/elasticsearch/verify_alerting_security'; import { disableWatcherClusterAlerts } from '../../../../lib/alerts/disable_watcher_cluster_alerts'; -import { Alert, AlertTypeParams } from '../../../../../../alerting/common'; +import { AlertTypeParams, SanitizedAlert } from '../../../../../../alerting/common'; const DEFAULT_SERVER_LOG_NAME = 'Monitoring: Write to Kibana log'; @@ -77,7 +77,7 @@ export function enableAlertsRoute(_server: unknown, npRoute: RouteDependencies) }, ]; - let createdAlerts: Array> = []; + let createdAlerts: Array> = []; const disabledWatcherClusterAlerts = await disableWatcherClusterAlerts( npRoute.cluster.asScoped(request).callAsCurrentUser, npRoute.logger diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/create_notifications.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/create_notifications.ts index b82e3052c39356..c445c335662895 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/create_notifications.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/create_notifications.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { Alert } from '../../../../../alerting/common'; +import { SanitizedAlert } from '../../../../../alerting/common'; import { SERVER_APP_ID, NOTIFICATIONS_ID } from '../../../../common/constants'; import { CreateNotificationParams, RuleNotificationAlertTypeParams } from './types'; import { addTags } from './add_tags'; @@ -18,7 +18,7 @@ export const createNotifications = async ({ ruleAlertId, interval, name, -}: CreateNotificationParams): Promise> => +}: CreateNotificationParams): Promise> => alertsClient.create({ data: { name, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.ts index e020810b6d03a9..a654dd6a10e329 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.ts @@ -6,7 +6,7 @@ */ import { transformRuleToAlertAction } from '../../../../common/detection_engine/transform_actions'; -import { Alert } from '../../../../../alerting/common'; +import { SanitizedAlert } from '../../../../../alerting/common'; import { SERVER_APP_ID, SIGNALS_ID } from '../../../../common/constants'; import { CreateRulesOptions } from './types'; import { addTags } from './add_tags'; @@ -62,7 +62,7 @@ export const createRules = async ({ version, exceptionsList, actions, -}: CreateRulesOptions): Promise> => { +}: CreateRulesOptions): Promise> => { return alertsClient.create({ data: { name, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/install_prepacked_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/install_prepacked_rules.ts index 31df1dd4193a4b..7efd63cc677222 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/install_prepacked_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/install_prepacked_rules.ts @@ -6,7 +6,7 @@ */ import { AddPrepackagedRulesSchemaDecoded } from '../../../../common/detection_engine/schemas/request/add_prepackaged_rules_schema'; -import { Alert, AlertTypeParams } from '../../../../../alerting/common'; +import { SanitizedAlert, AlertTypeParams } from '../../../../../alerting/common'; import { AlertsClient } from '../../../../../alerting/server'; import { createRules } from './create_rules'; import { PartialFilter } from '../types'; @@ -15,8 +15,8 @@ export const installPrepackagedRules = ( alertsClient: AlertsClient, rules: AddPrepackagedRulesSchemaDecoded[], outputIndex: string -): Array>> => - rules.reduce>>>((acc, rule) => { +): Array>> => + rules.reduce>>>((acc, rule) => { const { anomaly_threshold: anomalyThreshold, author, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/constants/index.ts b/x-pack/plugins/triggers_actions_ui/public/application/constants/index.ts index 2d0b2e99c10a69..8ac1fbaec403be 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/constants/index.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/constants/index.ts @@ -7,7 +7,7 @@ import { i18n } from '@kbn/i18n'; -export { BASE_ALERT_API_PATH } from '../../../../alerting/common'; +export { LEGACY_BASE_ALERT_API_PATH } from '../../../../alerting/common'; export { BASE_ACTION_API_PATH } from '../../../../actions/common'; export type Section = 'connectors' | 'rules'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.ts index 744e6472a9194c..80ff4155821914 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.ts @@ -11,7 +11,7 @@ import { pipe } from 'fp-ts/lib/pipeable'; import { fold } from 'fp-ts/lib/Either'; import { pick } from 'lodash'; import { alertStateSchema, AlertingFrameworkHealth } from '../../../../alerting/common'; -import { BASE_ALERT_API_PATH } from '../constants'; +import { LEGACY_BASE_ALERT_API_PATH } from '../constants'; import { Alert, AlertAggregations, @@ -24,7 +24,7 @@ import { } from '../../types'; export async function loadAlertTypes({ http }: { http: HttpSetup }): Promise { - return await http.get(`${BASE_ALERT_API_PATH}/list_alert_types`); + return await http.get(`${LEGACY_BASE_ALERT_API_PATH}/list_alert_types`); } export async function loadAlert({ @@ -34,7 +34,7 @@ export async function loadAlert({ http: HttpSetup; alertId: string; }): Promise { - return await http.get(`${BASE_ALERT_API_PATH}/alert/${alertId}`); + return await http.get(`${LEGACY_BASE_ALERT_API_PATH}/alert/${alertId}`); } type EmptyHttpResponse = ''; @@ -46,7 +46,7 @@ export async function loadAlertState({ alertId: string; }): Promise { return await http - .get(`${BASE_ALERT_API_PATH}/alert/${alertId}/state`) + .get(`${LEGACY_BASE_ALERT_API_PATH}/alert/${alertId}/state`) .then((state: AlertTaskState | EmptyHttpResponse) => (state ? state : {})) .then((state: AlertTaskState) => { return pipe( @@ -65,7 +65,7 @@ export async function loadAlertInstanceSummary({ http: HttpSetup; alertId: string; }): Promise { - return await http.get(`${BASE_ALERT_API_PATH}/alert/${alertId}/_instance_summary`); + return await http.get(`${LEGACY_BASE_ALERT_API_PATH}/alert/${alertId}/_instance_summary`); } export const mapFiltersToKql = ({ @@ -121,7 +121,7 @@ export async function loadAlerts({ data: Alert[]; }> { const filters = mapFiltersToKql({ typesFilter, actionTypesFilter, alertStatusesFilter }); - return await http.get(`${BASE_ALERT_API_PATH}/_find`, { + return await http.get(`${LEGACY_BASE_ALERT_API_PATH}/_find`, { query: { page: page.index + 1, per_page: page.size, @@ -149,7 +149,7 @@ export async function loadAlertAggregations({ alertStatusesFilter?: string[]; }): Promise { const filters = mapFiltersToKql({ typesFilter, actionTypesFilter, alertStatusesFilter }); - return await http.get(`${BASE_ALERT_API_PATH}/_aggregate`, { + return await http.get(`${LEGACY_BASE_ALERT_API_PATH}/_aggregate`, { query: { search_fields: searchText ? JSON.stringify(['name', 'tags']) : undefined, search: searchText, @@ -168,7 +168,7 @@ export async function deleteAlerts({ }): Promise<{ successes: string[]; errors: string[] }> { const successes: string[] = []; const errors: string[] = []; - await Promise.all(ids.map((id) => http.delete(`${BASE_ALERT_API_PATH}/alert/${id}`))).then( + await Promise.all(ids.map((id) => http.delete(`${LEGACY_BASE_ALERT_API_PATH}/alert/${id}`))).then( function (fulfilled) { successes.push(...fulfilled); }, @@ -189,7 +189,7 @@ export async function createAlert({ 'createdBy' | 'updatedBy' | 'muteAll' | 'mutedInstanceIds' | 'executionStatus' >; }): Promise { - return await http.post(`${BASE_ALERT_API_PATH}/alert`, { + return await http.post(`${LEGACY_BASE_ALERT_API_PATH}/alert`, { body: JSON.stringify(alert), }); } @@ -206,7 +206,7 @@ export async function updateAlert({ >; id: string; }): Promise { - return await http.put(`${BASE_ALERT_API_PATH}/alert/${id}`, { + return await http.put(`${LEGACY_BASE_ALERT_API_PATH}/alert/${id}`, { body: JSON.stringify( pick(alert, ['throttle', 'name', 'tags', 'schedule', 'params', 'actions', 'notifyWhen']) ), @@ -214,7 +214,7 @@ export async function updateAlert({ } export async function enableAlert({ id, http }: { id: string; http: HttpSetup }): Promise { - await http.post(`${BASE_ALERT_API_PATH}/alert/${id}/_enable`); + await http.post(`${LEGACY_BASE_ALERT_API_PATH}/alert/${id}/_enable`); } export async function enableAlerts({ @@ -228,7 +228,7 @@ export async function enableAlerts({ } export async function disableAlert({ id, http }: { id: string; http: HttpSetup }): Promise { - await http.post(`${BASE_ALERT_API_PATH}/alert/${id}/_disable`); + await http.post(`${LEGACY_BASE_ALERT_API_PATH}/alert/${id}/_disable`); } export async function disableAlerts({ @@ -250,7 +250,7 @@ export async function muteAlertInstance({ instanceId: string; http: HttpSetup; }): Promise { - await http.post(`${BASE_ALERT_API_PATH}/alert/${id}/alert_instance/${instanceId}/_mute`); + await http.post(`${LEGACY_BASE_ALERT_API_PATH}/alert/${id}/alert_instance/${instanceId}/_mute`); } export async function unmuteAlertInstance({ @@ -262,11 +262,11 @@ export async function unmuteAlertInstance({ instanceId: string; http: HttpSetup; }): Promise { - await http.post(`${BASE_ALERT_API_PATH}/alert/${id}/alert_instance/${instanceId}/_unmute`); + await http.post(`${LEGACY_BASE_ALERT_API_PATH}/alert/${id}/alert_instance/${instanceId}/_unmute`); } export async function muteAlert({ id, http }: { id: string; http: HttpSetup }): Promise { - await http.post(`${BASE_ALERT_API_PATH}/alert/${id}/_mute_all`); + await http.post(`${LEGACY_BASE_ALERT_API_PATH}/alert/${id}/_mute_all`); } export async function muteAlerts({ ids, http }: { ids: string[]; http: HttpSetup }): Promise { @@ -274,7 +274,7 @@ export async function muteAlerts({ ids, http }: { ids: string[]; http: HttpSetup } export async function unmuteAlert({ id, http }: { id: string; http: HttpSetup }): Promise { - await http.post(`${BASE_ALERT_API_PATH}/alert/${id}/_unmute_all`); + await http.post(`${LEGACY_BASE_ALERT_API_PATH}/alert/${id}/_unmute_all`); } export async function unmuteAlerts({ @@ -292,5 +292,5 @@ export async function alertingFrameworkHealth({ }: { http: HttpSetup; }): Promise { - return await http.get(`${BASE_ALERT_API_PATH}/_health`); + return await http.get(`${LEGACY_BASE_ALERT_API_PATH}/_health`); } diff --git a/x-pack/test/alerting_api_integration/basic/tests/alerts/basic_noop_alert_type.ts b/x-pack/test/alerting_api_integration/basic/tests/alerts/basic_noop_alert_type.ts index 617e430df13e70..805f4440909ec2 100644 --- a/x-pack/test/alerting_api_integration/basic/tests/alerts/basic_noop_alert_type.ts +++ b/x-pack/test/alerting_api_integration/basic/tests/alerts/basic_noop_alert_type.ts @@ -15,7 +15,7 @@ export default function basicAlertTest({ getService }: FtrProviderContext) { describe('basic alert', () => { it('should return 200 when creating a basic license alert', async () => { await supertest - .post(`/api/alerts/alert`) + .post(`/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); diff --git a/x-pack/test/alerting_api_integration/basic/tests/alerts/gold_noop_alert_type.ts b/x-pack/test/alerting_api_integration/basic/tests/alerts/gold_noop_alert_type.ts index 211d1acb2a0054..50e8760f2d8f51 100644 --- a/x-pack/test/alerting_api_integration/basic/tests/alerts/gold_noop_alert_type.ts +++ b/x-pack/test/alerting_api_integration/basic/tests/alerts/gold_noop_alert_type.ts @@ -15,9 +15,9 @@ export default function emailTest({ getService }: FtrProviderContext) { describe('create gold noop alert', () => { it('should return 403 when creating an gold alert', async () => { await supertest - .post(`/api/alerts/alert`) + .post(`/api/alerting/rule`) .set('kbn-xsrf', 'foo') - .send(getTestAlertData({ alertTypeId: 'test.gold.noop' })) + .send(getTestAlertData({ rule_type_id: 'test.gold.noop' })) .expect(403, { statusCode: 403, error: 'Forbidden', diff --git a/x-pack/test/alerting_api_integration/common/lib/alert_utils.ts b/x-pack/test/alerting_api_integration/common/lib/alert_utils.ts index fab4518b99b22b..540f8f4d1cad92 100644 --- a/x-pack/test/alerting_api_integration/common/lib/alert_utils.ts +++ b/x-pack/test/alerting_api_integration/common/lib/alert_utils.ts @@ -68,7 +68,7 @@ export class AlertUtils { public getEnableRequest(alertId: string) { const request = this.supertestWithoutAuth - .post(`${getUrlPrefix(this.space.id)}/api/alerts/alert/${alertId}/_enable`) + .post(`${getUrlPrefix(this.space.id)}/api/alerting/rule/${alertId}/_enable`) .set('kbn-xsrf', 'foo'); if (this.user) { return request.auth(this.user.username, this.user.password); @@ -78,7 +78,7 @@ export class AlertUtils { public getDisableRequest(alertId: string) { const request = this.supertestWithoutAuth - .post(`${getUrlPrefix(this.space.id)}/api/alerts/alert/${alertId}/_disable`) + .post(`${getUrlPrefix(this.space.id)}/api/alerting/rule/${alertId}/_disable`) .set('kbn-xsrf', 'foo'); if (this.user) { return request.auth(this.user.username, this.user.password); @@ -88,7 +88,7 @@ export class AlertUtils { public getMuteAllRequest(alertId: string) { const request = this.supertestWithoutAuth - .post(`${getUrlPrefix(this.space.id)}/api/alerts/alert/${alertId}/_mute_all`) + .post(`${getUrlPrefix(this.space.id)}/api/alerting/rule/${alertId}/_mute_all`) .set('kbn-xsrf', 'foo'); if (this.user) { return request.auth(this.user.username, this.user.password); @@ -98,7 +98,7 @@ export class AlertUtils { public getUnmuteAllRequest(alertId: string) { const request = this.supertestWithoutAuth - .post(`${getUrlPrefix(this.space.id)}/api/alerts/alert/${alertId}/_unmute_all`) + .post(`${getUrlPrefix(this.space.id)}/api/alerting/rule/${alertId}/_unmute_all`) .set('kbn-xsrf', 'foo'); if (this.user) { return request.auth(this.user.username, this.user.password); @@ -108,11 +108,7 @@ export class AlertUtils { public getMuteInstanceRequest(alertId: string, instanceId: string) { const request = this.supertestWithoutAuth - .post( - `${getUrlPrefix( - this.space.id - )}/api/alerts/alert/${alertId}/alert_instance/${instanceId}/_mute` - ) + .post(`${getUrlPrefix(this.space.id)}/api/alerting/rule/${alertId}/alert/${instanceId}/_mute`) .set('kbn-xsrf', 'foo'); if (this.user) { return request.auth(this.user.username, this.user.password); @@ -123,9 +119,7 @@ export class AlertUtils { public getUnmuteInstanceRequest(alertId: string, instanceId: string) { const request = this.supertestWithoutAuth .post( - `${getUrlPrefix( - this.space.id - )}/api/alerts/alert/${alertId}/alert_instance/${instanceId}/_unmute` + `${getUrlPrefix(this.space.id)}/api/alerting/rule/${alertId}/alert/${instanceId}/_unmute` ) .set('kbn-xsrf', 'foo'); if (this.user) { @@ -136,7 +130,7 @@ export class AlertUtils { public getUpdateApiKeyRequest(alertId: string) { const request = this.supertestWithoutAuth - .post(`${getUrlPrefix(this.space.id)}/api/alerts/alert/${alertId}/_update_api_key`) + .post(`${getUrlPrefix(this.space.id)}/internal/alerting/rule/${alertId}/_update_api_key`) .set('kbn-xsrf', 'foo'); if (this.user) { return request.auth(this.user.username, this.user.password); @@ -189,7 +183,7 @@ export class AlertUtils { } let request = this.supertestWithoutAuth - .post(`${getUrlPrefix(this.space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(this.space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo'); if (this.user) { request = request.auth(this.user.username, this.user.password); @@ -197,7 +191,7 @@ export class AlertUtils { const alertBody = getDefaultAlwaysFiringAlertData(reference, actionId); const response = await request.send({ ...alertBody, ...overwrites }); if (response.statusCode === 200) { - objRemover.add(this.space.id, response.body.id, 'alert', 'alerts'); + objRemover.add(this.space.id, response.body.id, 'rule', 'alerting'); } return response; } @@ -216,14 +210,16 @@ export class AlertUtils { } const request = this.supertestWithoutAuth - .put(`${getUrlPrefix(this.space.id)}/api/alerts/alert/${alertId}`) + .put(`${getUrlPrefix(this.space.id)}/api/alerting/rule/${alertId}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password); - const { alertTypeId, enabled, consumer, ...alertBody } = getDefaultAlwaysFiringAlertData( - reference, - actionId - ); + const { + rule_type_id: alertTypeId, + enabled, + consumer, + ...alertBody + } = getDefaultAlwaysFiringAlertData(reference, actionId); const response = await request.send({ ...alertBody, ...overwrites }); return response; @@ -246,7 +242,7 @@ export class AlertUtils { } let request = this.supertestWithoutAuth - .post(`${getUrlPrefix(this.space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(this.space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo'); if (this.user) { request = request.auth(this.user.username, this.user.password); @@ -257,17 +253,18 @@ export class AlertUtils { schedule: { interval: '30s' }, throttle: '30s', tags: [], - alertTypeId: 'test.failing', + rule_type_id: 'test.failing', consumer: 'alertsFixture', params: { index: ES_TEST_INDEX_NAME, reference, }, + notify_when: 'onActiveAlert', actions: [], ...overwrites, }); if (response.statusCode === 200) { - objRemover.add(this.space.id, response.body.id, 'alert', 'alerts'); + objRemover.add(this.space.id, response.body.id, 'rule', 'alerting'); } return response; } @@ -290,7 +287,7 @@ export class AlertUtils { } let request = this.supertestWithoutAuth - .post(`${getUrlPrefix(this.space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(this.space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo'); if (this.user) { request = request.auth(this.user.username, this.user.password); @@ -300,7 +297,7 @@ export class AlertUtils { ...overwrites, }); if (response.statusCode === 200) { - objRemover.add(this.space.id, response.body.id, 'alert', 'alerts'); + objRemover.add(this.space.id, response.body.id, 'rule', 'alerting'); } return response; } @@ -339,12 +336,13 @@ instanceStateValue: {{state.instanceStateValue}} schedule: { interval: '1m' }, throttle: '1m', tags: ['tag-A', 'tag-B'], - alertTypeId: 'test.always-firing', + rule_type_id: 'test.always-firing', consumer: 'alertsFixture', params: { index: ES_TEST_INDEX_NAME, reference, }, + notify_when: 'onActiveAlert', actions: [ { group: 'default', diff --git a/x-pack/test/alerting_api_integration/common/lib/get_test_alert_data.ts b/x-pack/test/alerting_api_integration/common/lib/get_test_alert_data.ts index 46624fcf86d372..22dc93b110a074 100644 --- a/x-pack/test/alerting_api_integration/common/lib/get_test_alert_data.ts +++ b/x-pack/test/alerting_api_integration/common/lib/get_test_alert_data.ts @@ -10,11 +10,11 @@ export function getTestAlertData(overwrites = {}) { enabled: true, name: 'abc', tags: ['foo'], - alertTypeId: 'test.noop', + rule_type_id: 'test.noop', consumer: 'alertsFixture', schedule: { interval: '1m' }, throttle: '1m', - notifyWhen: 'onThrottleInterval', + notify_when: 'onThrottleInterval', actions: [], params: {}, ...overwrites, diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/get_all.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/get_all.ts index 2142520894669b..1c227bc87d93f0 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/get_all.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/get_all.ts @@ -124,7 +124,7 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { objectRemover.add(space.id, createdAction.id, 'action', 'actions'); const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/alerts.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/alerts.ts index 536c4cbbd710f1..24e641cb06407d 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/alerts.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/alerts.ts @@ -56,11 +56,11 @@ export default function alertTests({ getService }: FtrProviderContext) { before(async () => { const { body: createdAction } = await supertest - .post(`${getUrlPrefix(space.id)}/api/actions/action`) + .post(`${getUrlPrefix(space.id)}/api/actions/connector`) .set('kbn-xsrf', 'foo') .send({ name: 'My action', - actionTypeId: 'test.index-record', + connector_type_id: 'test.index-record', config: { unencrypted: `This value shouldn't get encrypted`, }, @@ -78,7 +78,7 @@ export default function alertTests({ getService }: FtrProviderContext) { objectRemover, }); }); - after(() => objectRemover.add(space.id, indexRecordActionId, 'action', 'actions')); + after(() => objectRemover.add(space.id, indexRecordActionId, 'connector', 'actions')); it('should schedule task, run alert and schedule actions when appropriate', async () => { const testStart = new Date(); @@ -365,24 +365,24 @@ instanceStateValue: true const retryDate = new Date(Date.now() + 60000); const { body: createdAction } = await supertest - .post(`${getUrlPrefix(space.id)}/api/actions/action`) + .post(`${getUrlPrefix(space.id)}/api/actions/connector`) .set('kbn-xsrf', 'foo') .send({ name: 'Test rate limit', - actionTypeId: 'test.rate-limit', + connector_type_id: 'test.rate-limit', config: {}, }) .expect(200); - objectRemover.add(space.id, createdAction.id, 'action', 'actions'); + objectRemover.add(space.id, createdAction.id, 'connector', 'actions'); const reference = alertUtils.generateReference(); const response = await supertestWithoutAuth - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send( getTestAlertData({ - alertTypeId: 'test.always-firing', + rule_type_id: 'test.always-firing', params: { index: ES_TEST_INDEX_NAME, reference: 'create-test-2', @@ -428,7 +428,7 @@ instanceStateValue: true case 'space_1_all at space1': case 'space_1_all_with_restricted_fixture at space1': expect(response.statusCode).to.eql(200); - objectRemover.add(space.id, response.body.id, 'alert', 'alerts'); + objectRemover.add(space.id, response.body.id, 'rule', 'alerting'); // Wait for the task to be attempted once and idle const scheduledActionTask = await retry.try(async () => { @@ -482,12 +482,12 @@ instanceStateValue: true const testStart = new Date(); const reference = alertUtils.generateReference(); const response = await supertestWithoutAuth - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send( getTestAlertData({ - alertTypeId: 'test.authorization', + rule_type_id: 'test.authorization', params: { callClusterAuthorizationIndex: authorizationIndex, savedObjectsClientType: 'dashboard', @@ -517,7 +517,7 @@ instanceStateValue: true case 'space_1_all_alerts_none_actions at space1': case 'space_1_all_with_restricted_fixture at space1': expect(response.statusCode).to.eql(200); - objectRemover.add(space.id, response.body.id, 'alert', 'alerts'); + objectRemover.add(space.id, response.body.id, 'rule', 'alerting'); // Wait for test.authorization to index a document before disabling the alert and waiting for tasks to finish await esTestIndexTool.waitForDocs('alert:test.authorization', reference); @@ -548,7 +548,7 @@ instanceStateValue: true break; case 'superuser at space1': expect(response.statusCode).to.eql(200); - objectRemover.add(space.id, response.body.id, 'alert', 'alerts'); + objectRemover.add(space.id, response.body.id, 'rule', 'alerting'); // Wait for test.authorization to index a document before disabling the alert and waiting for tasks to finish await esTestIndexTool.waitForDocs('alert:test.authorization', reference); @@ -581,21 +581,21 @@ instanceStateValue: true const testStart = new Date(); const reference = alertUtils.generateReference(); const { body: createdAction } = await supertest - .post(`${getUrlPrefix(space.id)}/api/actions/action`) + .post(`${getUrlPrefix(space.id)}/api/actions/connector`) .set('kbn-xsrf', 'foo') .send({ name: 'My action', - actionTypeId: 'test.authorization', + connector_type_id: 'test.authorization', }) .expect(200); - objectRemover.add(space.id, createdAction.id, 'action', 'actions'); + objectRemover.add(space.id, createdAction.id, 'connector', 'actions'); const response = await supertestWithoutAuth - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send( getTestAlertData({ - alertTypeId: 'test.always-firing', + rule_type_id: 'test.always-firing', params: { index: ES_TEST_INDEX_NAME, reference, @@ -634,7 +634,7 @@ instanceStateValue: true case 'space_1_all at space1': case 'space_1_all_with_restricted_fixture at space1': expect(response.statusCode).to.eql(200); - objectRemover.add(space.id, response.body.id, 'alert', 'alerts'); + objectRemover.add(space.id, response.body.id, 'rule', 'alerting'); // Ensure test.authorization indexed 1 document before disabling the alert and waiting for tasks to finish await esTestIndexTool.waitForDocs('action:test.authorization', reference); @@ -673,7 +673,7 @@ instanceStateValue: true break; case 'superuser at space1': expect(response.statusCode).to.eql(200); - objectRemover.add(space.id, response.body.id, 'alert', 'alerts'); + objectRemover.add(space.id, response.body.id, 'rule', 'alerting'); // Ensure test.authorization indexed 1 document before disabling the alert and waiting for tasks to finish await esTestIndexTool.waitForDocs('action:test.authorization', reference); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/create.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/create.ts index 842a3cc11c72c3..70cafe407de296 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/create.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/create.ts @@ -40,18 +40,18 @@ export default function createAlertTests({ getService }: FtrProviderContext) { describe(scenario.id, () => { it('should handle create alert request appropriately', async () => { const { body: createdAction } = await supertest - .post(`${getUrlPrefix(space.id)}/api/actions/action`) + .post(`${getUrlPrefix(space.id)}/api/actions/connector`) .set('kbn-xsrf', 'foo') .send({ name: 'MY action', - actionTypeId: 'test.noop', + connector_type_id: 'test.noop', config: {}, secrets: {}, }) .expect(200); const response = await supertestWithoutAuth - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send( @@ -93,7 +93,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { case 'space_1_all at space1': case 'space_1_all_with_restricted_fixture at space1': expect(response.statusCode).to.eql(200); - objectRemover.add(space.id, response.body.id, 'alert', 'alerts'); + objectRemover.add(space.id, response.body.id, 'rule', 'alerting'); expect(response.body).to.eql({ id: response.body.id, name: 'abc', @@ -101,33 +101,35 @@ export default function createAlertTests({ getService }: FtrProviderContext) { actions: [ { id: createdAction.id, - actionTypeId: createdAction.actionTypeId, + connector_type_id: createdAction.connector_type_id, group: 'default', params: {}, }, ], enabled: true, - alertTypeId: 'test.noop', + rule_type_id: 'test.noop', consumer: 'alertsFixture', params: {}, - createdBy: user.username, + created_by: user.username, schedule: { interval: '1m' }, - scheduledTaskId: response.body.scheduledTaskId, - createdAt: response.body.createdAt, - updatedAt: response.body.updatedAt, + scheduled_task_id: response.body.scheduled_task_id, + created_at: response.body.created_at, + updated_at: response.body.updated_at, throttle: '1m', - notifyWhen: 'onThrottleInterval', - updatedBy: user.username, - apiKeyOwner: user.username, - muteAll: false, - mutedInstanceIds: [], - executionStatus: response.body.executionStatus, + notify_when: 'onThrottleInterval', + updated_by: user.username, + api_key_owner: user.username, + mute_all: false, + muted_alert_ids: [], + execution_status: response.body.execution_status, }); - expect(typeof response.body.scheduledTaskId).to.be('string'); - expect(Date.parse(response.body.createdAt)).to.be.greaterThan(0); - expect(Date.parse(response.body.updatedAt)).to.be.greaterThan(0); + expect(typeof response.body.scheduled_task_id).to.be('string'); + expect(Date.parse(response.body.created_at)).to.be.greaterThan(0); + expect(Date.parse(response.body.updated_at)).to.be.greaterThan(0); - const { _source: taskRecord } = await getScheduledTask(response.body.scheduledTaskId); + const { _source: taskRecord } = await getScheduledTask( + response.body.scheduled_task_id + ); expect(taskRecord.type).to.eql('task'); expect(taskRecord.task.taskType).to.eql('alerting:test.noop'); expect(JSON.parse(taskRecord.task.params)).to.eql({ @@ -149,12 +151,12 @@ export default function createAlertTests({ getService }: FtrProviderContext) { it('should handle create alert request appropriately when consumer is the same as producer', async () => { const response = await supertestWithoutAuth - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send( getTestAlertData({ - alertTypeId: 'test.restricted-noop', + rule_type_id: 'test.restricted-noop', consumer: 'alertsRestrictedFixture', }) ); @@ -179,7 +181,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { case 'superuser at space1': case 'space_1_all_with_restricted_fixture at space1': expect(response.statusCode).to.eql(200); - objectRemover.add(space.id, response.body.id, 'alert', 'alerts'); + objectRemover.add(space.id, response.body.id, 'rule', 'alerting'); break; default: throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); @@ -188,11 +190,14 @@ export default function createAlertTests({ getService }: FtrProviderContext) { it('should handle create alert request appropriately when consumer is not the producer', async () => { const response = await supertestWithoutAuth - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send( - getTestAlertData({ alertTypeId: 'test.unrestricted-noop', consumer: 'alertsFixture' }) + getTestAlertData({ + rule_type_id: 'test.unrestricted-noop', + consumer: 'alertsFixture', + }) ); switch (scenario.id) { @@ -226,7 +231,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { case 'superuser at space1': case 'space_1_all_with_restricted_fixture at space1': expect(response.statusCode).to.eql(200); - objectRemover.add(space.id, response.body.id, 'alert', 'alerts'); + objectRemover.add(space.id, response.body.id, 'rule', 'alerting'); break; default: throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); @@ -235,12 +240,12 @@ export default function createAlertTests({ getService }: FtrProviderContext) { it('should handle create alert request appropriately when consumer is "alerts"', async () => { const response = await supertestWithoutAuth - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send( getTestAlertData({ - alertTypeId: 'test.noop', + rule_type_id: 'test.noop', consumer: 'alerts', }) ); @@ -272,7 +277,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { case 'superuser at space1': case 'space_1_all_with_restricted_fixture at space1': expect(response.statusCode).to.eql(200); - objectRemover.add(space.id, response.body.id, 'alert', 'alerts'); + objectRemover.add(space.id, response.body.id, 'rule', 'alerting'); break; default: throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); @@ -281,12 +286,12 @@ export default function createAlertTests({ getService }: FtrProviderContext) { it('should handle create alert request appropriately when consumer is unknown', async () => { const response = await supertestWithoutAuth - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send( getTestAlertData({ - alertTypeId: 'test.noop', + rule_type_id: 'test.noop', consumer: 'some consumer patrick invented', }) ); @@ -317,7 +322,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { it('should handle create alert request appropriately when an alert is disabled ', async () => { const response = await supertestWithoutAuth - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send(getTestAlertData({ enabled: false })); @@ -342,8 +347,8 @@ export default function createAlertTests({ getService }: FtrProviderContext) { case 'space_1_all_alerts_none_actions at space1': case 'space_1_all_with_restricted_fixture at space1': expect(response.statusCode).to.eql(200); - objectRemover.add(space.id, response.body.id, 'alert', 'alerts'); - expect(response.body.scheduledTaskId).to.eql(undefined); + objectRemover.add(space.id, response.body.id, 'rule', 'alerting'); + expect(response.body.scheduled_task_id).to.eql(undefined); break; default: throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); @@ -352,7 +357,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { it('should handle create alert request appropriately when alert name has leading and trailing whitespaces', async () => { const response = await supertestWithoutAuth - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send( @@ -382,7 +387,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { case 'space_1_all_with_restricted_fixture at space1': expect(response.statusCode).to.eql(200); expect(response.body.name).to.eql(' leading and trailing whitespace '); - objectRemover.add(space.id, response.body.id, 'alert', 'alerts'); + objectRemover.add(space.id, response.body.id, 'rule', 'alerting'); break; default: throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); @@ -391,12 +396,12 @@ export default function createAlertTests({ getService }: FtrProviderContext) { it('should handle create alert request appropriately when alert type is unregistered', async () => { const response = await supertestWithoutAuth - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send( getTestAlertData({ - alertTypeId: 'test.unregistered-alert-type', + rule_type_id: 'test.unregistered-alert-type', }) ); @@ -422,7 +427,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { it('should handle create alert request appropriately when payload is empty and invalid', async () => { const response = await supertestWithoutAuth - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send({}); @@ -449,12 +454,12 @@ export default function createAlertTests({ getService }: FtrProviderContext) { it(`should handle create alert request appropriately when params isn't valid`, async () => { const response = await supertestWithoutAuth - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send( getTestAlertData({ - alertTypeId: 'test.validation', + rule_type_id: 'test.validation', }) ); @@ -492,7 +497,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { it('should handle create alert request appropriately when interval schedule is wrong syntax', async () => { const response = await supertestWithoutAuth - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send(getTestAlertData(getTestAlertData({ schedule: { interval: '10x' } }))); @@ -519,7 +524,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { it('should handle create alert request appropriately when interval schedule is 0', async () => { const response = await supertestWithoutAuth - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send(getTestAlertData(getTestAlertData({ schedule: { interval: '0s' } }))); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/delete.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/delete.ts index 3734e4ebe7c8af..2cbb16ababd10b 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/delete.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/delete.ts @@ -40,13 +40,13 @@ export default function createDeleteTests({ getService }: FtrProviderContext) { describe(scenario.id, () => { it('should handle delete alert request appropriately', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); const response = await supertestWithoutAuth - .delete(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) + .delete(`${getUrlPrefix(space.id)}/api/alerting/rule/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password); @@ -64,9 +64,9 @@ export default function createDeleteTests({ getService }: FtrProviderContext) { ), statusCode: 403, }); - objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); // Ensure task still exists - await getScheduledTask(createdAlert.scheduledTaskId); + await getScheduledTask(createdAlert.scheduled_task_id); break; case 'superuser at space1': case 'space_1_all at space1': @@ -75,7 +75,7 @@ export default function createDeleteTests({ getService }: FtrProviderContext) { expect(response.statusCode).to.eql(204); expect(response.body).to.eql(''); try { - await getScheduledTask(createdAlert.scheduledTaskId); + await getScheduledTask(createdAlert.scheduled_task_id); throw new Error('Should have removed scheduled task'); } catch (e) { expect(e.status).to.eql(404); @@ -88,18 +88,18 @@ export default function createDeleteTests({ getService }: FtrProviderContext) { it('should handle delete alert request appropriately when consumer is the same as producer', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ - alertTypeId: 'test.restricted-noop', + rule_type_id: 'test.restricted-noop', consumer: 'alertsRestrictedFixture', }) ) .expect(200); const response = await supertestWithoutAuth - .delete(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) + .delete(`${getUrlPrefix(space.id)}/api/alerting/rule/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password); @@ -119,16 +119,16 @@ export default function createDeleteTests({ getService }: FtrProviderContext) { ), statusCode: 403, }); - objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); // Ensure task still exists - await getScheduledTask(createdAlert.scheduledTaskId); + await getScheduledTask(createdAlert.scheduled_task_id); break; case 'superuser at space1': case 'space_1_all_with_restricted_fixture at space1': expect(response.statusCode).to.eql(204); expect(response.body).to.eql(''); try { - await getScheduledTask(createdAlert.scheduledTaskId); + await getScheduledTask(createdAlert.scheduled_task_id); throw new Error('Should have removed scheduled task'); } catch (e) { expect(e.status).to.eql(404); @@ -141,15 +141,18 @@ export default function createDeleteTests({ getService }: FtrProviderContext) { it('should handle delete alert request appropriately when consumer is not the producer', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( - getTestAlertData({ alertTypeId: 'test.unrestricted-noop', consumer: 'alertsFixture' }) + getTestAlertData({ + rule_type_id: 'test.unrestricted-noop', + consumer: 'alertsFixture', + }) ) .expect(200); const response = await supertestWithoutAuth - .delete(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) + .delete(`${getUrlPrefix(space.id)}/api/alerting/rule/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password); @@ -167,9 +170,9 @@ export default function createDeleteTests({ getService }: FtrProviderContext) { ), statusCode: 403, }); - objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); // Ensure task still exists - await getScheduledTask(createdAlert.scheduledTaskId); + await getScheduledTask(createdAlert.scheduled_task_id); break; case 'space_1_all at space1': case 'space_1_all_alerts_none_actions at space1': @@ -183,16 +186,16 @@ export default function createDeleteTests({ getService }: FtrProviderContext) { ), statusCode: 403, }); - objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); // Ensure task still exists - await getScheduledTask(createdAlert.scheduledTaskId); + await getScheduledTask(createdAlert.scheduled_task_id); break; case 'superuser at space1': case 'space_1_all_with_restricted_fixture at space1': expect(response.statusCode).to.eql(204); expect(response.body).to.eql(''); try { - await getScheduledTask(createdAlert.scheduledTaskId); + await getScheduledTask(createdAlert.scheduled_task_id); throw new Error('Should have removed scheduled task'); } catch (e) { expect(e.status).to.eql(404); @@ -205,18 +208,18 @@ export default function createDeleteTests({ getService }: FtrProviderContext) { it('should handle delete alert request appropriately when consumer is "alerts"', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ - alertTypeId: 'test.noop', + rule_type_id: 'test.noop', consumer: 'alerts', }) ) .expect(200); const response = await supertestWithoutAuth - .delete(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) + .delete(`${getUrlPrefix(space.id)}/api/alerting/rule/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password); @@ -241,9 +244,9 @@ export default function createDeleteTests({ getService }: FtrProviderContext) { ), statusCode: 403, }); - objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); // Ensure task still exists - await getScheduledTask(createdAlert.scheduledTaskId); + await getScheduledTask(createdAlert.scheduled_task_id); break; case 'superuser at space1': case 'space_1_all at space1': @@ -252,7 +255,7 @@ export default function createDeleteTests({ getService }: FtrProviderContext) { expect(response.statusCode).to.eql(204); expect(response.body).to.eql(''); try { - await getScheduledTask(createdAlert.scheduledTaskId); + await getScheduledTask(createdAlert.scheduled_task_id); throw new Error('Should have removed scheduled task'); } catch (e) { expect(e.status).to.eql(404); @@ -265,14 +268,14 @@ export default function createDeleteTests({ getService }: FtrProviderContext) { it(`shouldn't delete alert from another space`, async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); const response = await supertestWithoutAuth - .delete(`${getUrlPrefix('other')}/api/alerts/alert/${createdAlert.id}`) + .delete(`${getUrlPrefix('other')}/api/alerting/rule/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password); @@ -298,7 +301,7 @@ export default function createDeleteTests({ getService }: FtrProviderContext) { it('should still be able to delete alert when AAD is broken', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); @@ -318,7 +321,7 @@ export default function createDeleteTests({ getService }: FtrProviderContext) { }); const response = await supertestWithoutAuth - .delete(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) + .delete(`${getUrlPrefix(space.id)}/api/alerting/rule/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password); @@ -336,9 +339,9 @@ export default function createDeleteTests({ getService }: FtrProviderContext) { ), statusCode: 403, }); - objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); // Ensure task still exists - await getScheduledTask(createdAlert.scheduledTaskId); + await getScheduledTask(createdAlert.scheduled_task_id); break; case 'superuser at space1': case 'space_1_all at space1': @@ -347,7 +350,7 @@ export default function createDeleteTests({ getService }: FtrProviderContext) { expect(response.statusCode).to.eql(204); expect(response.body).to.eql(''); try { - await getScheduledTask(createdAlert.scheduledTaskId); + await getScheduledTask(createdAlert.scheduled_task_id); throw new Error('Should have removed scheduled task'); } catch (e) { expect(e.status).to.eql(404); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/disable.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/disable.ts index 17f4bdf61b2209..b265451bbd632b 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/disable.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/disable.ts @@ -44,18 +44,18 @@ export default function createDisableAlertTests({ getService }: FtrProviderConte describe(scenario.id, () => { it('should handle disable alert request appropriately', async () => { const { body: createdAction } = await supertest - .post(`${getUrlPrefix(space.id)}/api/actions/action`) + .post(`${getUrlPrefix(space.id)}/api/actions/connector`) .set('kbn-xsrf', 'foo') .send({ name: 'MY action', - actionTypeId: 'test.noop', + connector_type_id: 'test.noop', config: {}, secrets: {}, }) .expect(200); const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ @@ -70,7 +70,7 @@ export default function createDisableAlertTests({ getService }: FtrProviderConte }) ) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); const response = await alertUtils.getDisableRequest(createdAlert.id); @@ -89,7 +89,7 @@ export default function createDisableAlertTests({ getService }: FtrProviderConte statusCode: 403, }); // Ensure task still exists - await getScheduledTask(createdAlert.scheduledTaskId); + await getScheduledTask(createdAlert.scheduled_task_id); break; case 'space_1_all_alerts_none_actions at space1': case 'superuser at space1': @@ -98,7 +98,7 @@ export default function createDisableAlertTests({ getService }: FtrProviderConte expect(response.statusCode).to.eql(204); expect(response.body).to.eql(''); try { - await getScheduledTask(createdAlert.scheduledTaskId); + await getScheduledTask(createdAlert.scheduled_task_id); throw new Error('Should have removed scheduled task'); } catch (e) { expect(e.status).to.eql(404); @@ -118,17 +118,17 @@ export default function createDisableAlertTests({ getService }: FtrProviderConte it('should handle disable alert request appropriately when consumer is the same as producer', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ - alertTypeId: 'test.restricted-noop', + rule_type_id: 'test.restricted-noop', consumer: 'alertsRestrictedFixture', enabled: true, }) ) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); const response = await alertUtils.getDisableRequest(createdAlert.id); @@ -154,7 +154,7 @@ export default function createDisableAlertTests({ getService }: FtrProviderConte expect(response.statusCode).to.eql(204); expect(response.body).to.eql(''); try { - await getScheduledTask(createdAlert.scheduledTaskId); + await getScheduledTask(createdAlert.scheduled_task_id); throw new Error('Should have removed scheduled task'); } catch (e) { expect(e.status).to.eql(404); @@ -167,17 +167,17 @@ export default function createDisableAlertTests({ getService }: FtrProviderConte it('should handle disable alert request appropriately when consumer is not the producer', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ - alertTypeId: 'test.unrestricted-noop', + rule_type_id: 'test.unrestricted-noop', consumer: 'alertsFixture', enabled: true, }) ) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); const response = await alertUtils.getDisableRequest(createdAlert.id); @@ -214,7 +214,7 @@ export default function createDisableAlertTests({ getService }: FtrProviderConte expect(response.statusCode).to.eql(204); expect(response.body).to.eql(''); try { - await getScheduledTask(createdAlert.scheduledTaskId); + await getScheduledTask(createdAlert.scheduled_task_id); throw new Error('Should have removed scheduled task'); } catch (e) { expect(e.status).to.eql(404); @@ -227,17 +227,17 @@ export default function createDisableAlertTests({ getService }: FtrProviderConte it('should handle disable alert request appropriately when consumer is "alerts"', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ - alertTypeId: 'test.noop', + rule_type_id: 'test.noop', consumer: 'alerts', enabled: true, }) ) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); const response = await alertUtils.getDisableRequest(createdAlert.id); @@ -270,7 +270,7 @@ export default function createDisableAlertTests({ getService }: FtrProviderConte expect(response.statusCode).to.eql(204); expect(response.body).to.eql(''); try { - await getScheduledTask(createdAlert.scheduledTaskId); + await getScheduledTask(createdAlert.scheduled_task_id); throw new Error('Should have removed scheduled task'); } catch (e) { expect(e.status).to.eql(404); @@ -283,11 +283,11 @@ export default function createDisableAlertTests({ getService }: FtrProviderConte it('should still be able to disable alert when AAD is broken', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send(getTestAlertData({ enabled: true })) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); await retry.try(async () => { await supertest @@ -320,7 +320,7 @@ export default function createDisableAlertTests({ getService }: FtrProviderConte statusCode: 403, }); // Ensure task still exists - await getScheduledTask(createdAlert.scheduledTaskId); + await getScheduledTask(createdAlert.scheduled_task_id); break; case 'superuser at space1': case 'space_1_all at space1': @@ -329,7 +329,7 @@ export default function createDisableAlertTests({ getService }: FtrProviderConte expect(response.statusCode).to.eql(204); expect(response.body).to.eql(''); try { - await getScheduledTask(createdAlert.scheduledTaskId); + await getScheduledTask(createdAlert.scheduled_task_id); throw new Error('Should have removed scheduled task'); } catch (e) { expect(e.status).to.eql(404); @@ -349,11 +349,11 @@ export default function createDisableAlertTests({ getService }: FtrProviderConte it(`shouldn't disable alert from another space`, async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix('other')}/api/alerts/alert`) + .post(`${getUrlPrefix('other')}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send(getTestAlertData({ enabled: true })) .expect(200); - objectRemover.add('other', createdAlert.id, 'alert', 'alerts'); + objectRemover.add('other', createdAlert.id, 'rule', 'alerting'); const response = await alertUtils.getDisableRequest(createdAlert.id); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/enable.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/enable.ts index 262a1a791a99cd..70e286b7957206 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/enable.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/enable.ts @@ -44,18 +44,18 @@ export default function createEnableAlertTests({ getService }: FtrProviderContex describe(scenario.id, () => { it('should handle enable alert request appropriately', async () => { const { body: createdAction } = await supertest - .post(`${getUrlPrefix(space.id)}/api/actions/action`) + .post(`${getUrlPrefix(space.id)}/api/actions/connector`) .set('kbn-xsrf', 'foo') .send({ name: 'MY action', - actionTypeId: 'test.noop', + connector_type_id: 'test.noop', config: {}, secrets: {}, }) .expect(200); const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ @@ -70,7 +70,7 @@ export default function createEnableAlertTests({ getService }: FtrProviderContex }) ) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); const response = await alertUtils.getEnableRequest(createdAlert.id); @@ -114,12 +114,14 @@ export default function createEnableAlertTests({ getService }: FtrProviderContex expect(response.statusCode).to.eql(204); expect(response.body).to.eql(''); const { body: updatedAlert } = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) + .get(`${getUrlPrefix(space.id)}/api/alerting/rule/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .expect(200); - expect(typeof updatedAlert.scheduledTaskId).to.eql('string'); - const { _source: taskRecord } = await getScheduledTask(updatedAlert.scheduledTaskId); + expect(typeof updatedAlert.scheduled_task_id).to.eql('string'); + const { _source: taskRecord } = await getScheduledTask( + updatedAlert.scheduled_task_id + ); expect(taskRecord.type).to.eql('task'); expect(taskRecord.task.taskType).to.eql('alerting:test.noop'); expect(JSON.parse(taskRecord.task.params)).to.eql({ @@ -141,17 +143,17 @@ export default function createEnableAlertTests({ getService }: FtrProviderContex it('should handle enable alert request appropriately when consumer is the same as producer', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ - alertTypeId: 'test.restricted-noop', + rule_type_id: 'test.restricted-noop', consumer: 'alertsRestrictedFixture', enabled: false, }) ) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); const response = await alertUtils.getEnableRequest(createdAlert.id); @@ -177,7 +179,7 @@ export default function createEnableAlertTests({ getService }: FtrProviderContex expect(response.statusCode).to.eql(204); expect(response.body).to.eql(''); try { - await getScheduledTask(createdAlert.scheduledTaskId); + await getScheduledTask(createdAlert.scheduled_task_id); throw new Error('Should have removed scheduled task'); } catch (e) { expect(e.status).to.eql(404); @@ -190,17 +192,17 @@ export default function createEnableAlertTests({ getService }: FtrProviderContex it('should handle enable alert request appropriately when consumer is not the producer', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ - alertTypeId: 'test.unrestricted-noop', + rule_type_id: 'test.unrestricted-noop', consumer: 'alertsFixture', enabled: false, }) ) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); const response = await alertUtils.getEnableRequest(createdAlert.id); @@ -244,17 +246,17 @@ export default function createEnableAlertTests({ getService }: FtrProviderContex it('should handle enable alert request appropriately when consumer is "alerts"', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ - alertTypeId: 'test.noop', + rule_type_id: 'test.noop', consumer: 'alerts', enabled: false, }) ) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); const response = await alertUtils.getEnableRequest(createdAlert.id); @@ -287,7 +289,7 @@ export default function createEnableAlertTests({ getService }: FtrProviderContex expect(response.statusCode).to.eql(204); expect(response.body).to.eql(''); try { - await getScheduledTask(createdAlert.scheduledTaskId); + await getScheduledTask(createdAlert.scheduled_task_id); throw new Error('Should have removed scheduled task'); } catch (e) { expect(e.status).to.eql(404); @@ -300,11 +302,11 @@ export default function createEnableAlertTests({ getService }: FtrProviderContex it('should still be able to enable alert when AAD is broken', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send(getTestAlertData({ enabled: false })) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); await retry.try(async () => { await supertest @@ -344,12 +346,14 @@ export default function createEnableAlertTests({ getService }: FtrProviderContex expect(response.statusCode).to.eql(204); expect(response.body).to.eql(''); const { body: updatedAlert } = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) + .get(`${getUrlPrefix(space.id)}/api/alerting/rule/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .expect(200); - expect(typeof updatedAlert.scheduledTaskId).to.eql('string'); - const { _source: taskRecord } = await getScheduledTask(updatedAlert.scheduledTaskId); + expect(typeof updatedAlert.scheduled_task_id).to.eql('string'); + const { _source: taskRecord } = await getScheduledTask( + updatedAlert.scheduled_task_id + ); expect(taskRecord.type).to.eql('task'); expect(taskRecord.task.taskType).to.eql('alerting:test.noop'); expect(JSON.parse(taskRecord.task.params)).to.eql({ @@ -371,11 +375,11 @@ export default function createEnableAlertTests({ getService }: FtrProviderContex it(`shouldn't enable alert from another space`, async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix('other')}/api/alerts/alert`) + .post(`${getUrlPrefix('other')}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send(getTestAlertData({ enabled: false })) .expect(200); - objectRemover.add('other', createdAlert.id, 'alert', 'alerts'); + objectRemover.add('other', createdAlert.id, 'rule', 'alerting'); const response = await alertUtils.getEnableRequest(createdAlert.id); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/event_log.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/event_log.ts index 2561a8f3a7d488..825ade55cb4b05 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/event_log.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/event_log.ts @@ -24,11 +24,11 @@ export default function eventLogTests({ getService }: FtrProviderContext) { it('should generate events for alert decrypt errors', async () => { const spaceId = Spaces[0].id; const response = await supertest - .post(`${getUrlPrefix(spaceId)}/api/alerts/alert`) + .post(`${getUrlPrefix(spaceId)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ - alertTypeId: 'test.noop', + rule_type_id: 'test.noop', schedule: { interval: '1s' }, throttle: null, }) @@ -36,7 +36,7 @@ export default function eventLogTests({ getService }: FtrProviderContext) { expect(response.status).to.eql(200); const alertId = response.body.id; - objectRemover.add(spaceId, alertId, 'alert', 'alerts'); + objectRemover.add(spaceId, alertId, 'rule', 'alerting'); await retry.try(async () => { // break AAD diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/execution_status.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/execution_status.ts index 18f0be353b7da2..2bae1c541bc485 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/execution_status.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/execution_status.ts @@ -25,17 +25,17 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon it('should eventually have error reason "decrypt" when appropriate', async () => { const response = await supertest - .post(`${getUrlPrefix(spaceId)}/api/alerts/alert`) + .post(`${getUrlPrefix(spaceId)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ - alertTypeId: 'test.noop', + rule_type_id: 'test.noop', schedule: { interval: '1s' }, }) ); expect(response.status).to.eql(200); const alertId = response.body.id; - objectRemover.add(spaceId, alertId, 'alert', 'alerts'); + objectRemover.add(spaceId, alertId, 'rule', 'alerting'); let executionStatus = await waitForStatus(alertId, new Set(['ok']), 10000); @@ -70,15 +70,15 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon expect().fail(`waiting for alert ${id} statuses ${Array.from(statuses)} timed out`); } - const response = await supertest.get(`${getUrlPrefix(spaceId)}/api/alerts/alert/${id}`); + const response = await supertest.get(`${getUrlPrefix(spaceId)}/api/alerting/rule/${id}`); expect(response.status).to.eql(200); - const { status } = response.body.executionStatus; - if (statuses.has(status)) return response.body.executionStatus; + const { status } = response.body.execution_status; + if (statuses.has(status)) return response.body.execution_status; // eslint-disable-next-line no-console console.log( `waitForStatus(${Array.from(statuses)}): got ${JSON.stringify( - response.body.executionStatus + response.body.execution_status )}, retrying` ); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/find.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/find.ts index 29a5ef6fdf1860..dda5970904f8d8 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/find.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/find.ts @@ -27,17 +27,17 @@ export default function createFindTests({ getService }: FtrProviderContext) { describe(scenario.id, () => { it('should handle find alert request appropriately', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); const response = await supertestWithoutAuth .get( `${getUrlPrefix( space.id - )}/api/alerts/_find?search=test.noop&search_fields=alertTypeId` + )}/api/alerting/rules/_find?search=test.noop&search_fields=alertTypeId` ) .auth(user.username, user.password); @@ -58,33 +58,33 @@ export default function createFindTests({ getService }: FtrProviderContext) { case 'space_1_all_with_restricted_fixture at space1': expect(response.statusCode).to.eql(200); expect(response.body.page).to.equal(1); - expect(response.body.perPage).to.be.greaterThan(0); + expect(response.body.per_page).to.be.greaterThan(0); expect(response.body.total).to.be.greaterThan(0); const match = response.body.data.find((obj: any) => obj.id === createdAlert.id); expect(match).to.eql({ id: createdAlert.id, name: 'abc', tags: ['foo'], - alertTypeId: 'test.noop', + rule_type_id: 'test.noop', consumer: 'alertsFixture', schedule: { interval: '1m' }, enabled: true, actions: [], params: {}, - createdBy: 'elastic', - scheduledTaskId: match.scheduledTaskId, - createdAt: match.createdAt, - updatedAt: match.updatedAt, + created_by: 'elastic', + scheduled_task_id: match.scheduled_task_id, + created_at: match.created_at, + updated_at: match.updated_at, throttle: '1m', - notifyWhen: 'onThrottleInterval', - updatedBy: 'elastic', - apiKeyOwner: 'elastic', - muteAll: false, - mutedInstanceIds: [], - executionStatus: match.executionStatus, + notify_when: 'onThrottleInterval', + updated_by: 'elastic', + api_key_owner: 'elastic', + mute_all: false, + muted_alert_ids: [], + execution_status: match.execution_status, }); - expect(Date.parse(match.createdAt)).to.be.greaterThan(0); - expect(Date.parse(match.updatedAt)).to.be.greaterThan(0); + expect(Date.parse(match.created_at)).to.be.greaterThan(0); + expect(Date.parse(match.updated_at)).to.be.greaterThan(0); break; default: throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); @@ -95,25 +95,25 @@ export default function createFindTests({ getService }: FtrProviderContext) { async function createNoOpAlert(overrides = {}) { const alert = getTestAlertData(overrides); const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send(alert) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); return { id: createdAlert.id, - alertTypeId: alert.alertTypeId, + rule_type_id: alert.rule_type_id, }; } function createRestrictedNoOpAlert() { return createNoOpAlert({ - alertTypeId: 'test.restricted-noop', + rule_type_id: 'test.restricted-noop', consumer: 'alertsRestrictedFixture', }); } function createUnrestrictedNoOpAlert() { return createNoOpAlert({ - alertTypeId: 'test.unrestricted-noop', + rule_type_id: 'test.unrestricted-noop', consumer: 'alertsFixture', }); } @@ -131,7 +131,9 @@ export default function createFindTests({ getService }: FtrProviderContext) { const response = await supertestWithoutAuth .get( - `${getUrlPrefix(space.id)}/api/alerts/_find?per_page=${perPage}&sort_field=createdAt` + `${getUrlPrefix( + space.id + )}/api/alerting/rules/_find?per_page=${perPage}&sort_field=createdAt` ) .auth(user.username, user.password); @@ -149,12 +151,12 @@ export default function createFindTests({ getService }: FtrProviderContext) { case 'space_1_all_alerts_none_actions at space1': expect(response.statusCode).to.eql(200); expect(response.body.page).to.equal(1); - expect(response.body.perPage).to.be.equal(perPage); + expect(response.body.per_page).to.be.equal(perPage); expect(response.body.total).to.be.equal(6); { const [firstPage] = chunk( allAlerts - .filter((alert) => alert.alertTypeId !== 'test.restricted-noop') + .filter((alert) => alert.rule_type_id !== 'test.restricted-noop') .map((alert) => alert.id), perPage ); @@ -166,7 +168,7 @@ export default function createFindTests({ getService }: FtrProviderContext) { case 'space_1_all_with_restricted_fixture at space1': expect(response.statusCode).to.eql(200); expect(response.body.page).to.equal(1); - expect(response.body.perPage).to.be.equal(perPage); + expect(response.body.per_page).to.be.equal(perPage); expect(response.body.total).to.be.equal(8); { @@ -180,7 +182,7 @@ export default function createFindTests({ getService }: FtrProviderContext) { .get( `${getUrlPrefix( space.id - )}/api/alerts/_find?per_page=${perPage}&sort_field=createdAt&page=2` + )}/api/alerting/rules/_find?per_page=${perPage}&sort_field=createdAt&page=2` ) .auth(user.username, user.password); @@ -195,18 +197,18 @@ export default function createFindTests({ getService }: FtrProviderContext) { it('should handle find alert request with filter appropriately', async () => { const { body: createdAction } = await supertest - .post(`${getUrlPrefix(space.id)}/api/actions/action`) + .post(`${getUrlPrefix(space.id)}/api/actions/connector`) .set('kbn-xsrf', 'foo') .send({ name: 'My action', - actionTypeId: 'test.noop', + connector_type_id: 'test.noop', config: {}, secrets: {}, }) .expect(200); const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ @@ -221,13 +223,13 @@ export default function createFindTests({ getService }: FtrProviderContext) { }) ) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); const response = await supertestWithoutAuth .get( `${getUrlPrefix( space.id - )}/api/alerts/_find?filter=alert.attributes.actions:{ actionTypeId: test.noop }` + )}/api/alerting/rules/_find?filter=alert.attributes.actions:{ actionTypeId: test.noop }` ) .auth(user.username, user.password); @@ -248,14 +250,14 @@ export default function createFindTests({ getService }: FtrProviderContext) { case 'space_1_all_with_restricted_fixture at space1': expect(response.statusCode).to.eql(200); expect(response.body.page).to.equal(1); - expect(response.body.perPage).to.be.greaterThan(0); + expect(response.body.per_page).to.be.greaterThan(0); expect(response.body.total).to.be.greaterThan(0); const match = response.body.data.find((obj: any) => obj.id === createdAlert.id); expect(match).to.eql({ id: createdAlert.id, name: 'abc', tags: ['foo'], - alertTypeId: 'test.noop', + rule_type_id: 'test.noop', consumer: 'alertsFixture', schedule: { interval: '1m' }, enabled: false, @@ -263,24 +265,24 @@ export default function createFindTests({ getService }: FtrProviderContext) { { id: createdAction.id, group: 'default', - actionTypeId: 'test.noop', + connector_type_id: 'test.noop', params: {}, }, ], params: {}, - createdBy: 'elastic', + created_by: 'elastic', throttle: '1m', - updatedBy: 'elastic', - apiKeyOwner: null, - muteAll: false, - mutedInstanceIds: [], - notifyWhen: 'onThrottleInterval', - createdAt: match.createdAt, - updatedAt: match.updatedAt, - executionStatus: match.executionStatus, + updated_by: 'elastic', + api_key_owner: null, + mute_all: false, + muted_alert_ids: [], + notify_when: 'onThrottleInterval', + created_at: match.created_at, + updated_at: match.updated_at, + execution_status: match.execution_status, }); - expect(Date.parse(match.createdAt)).to.be.greaterThan(0); - expect(Date.parse(match.updatedAt)).to.be.greaterThan(0); + expect(Date.parse(match.created_at)).to.be.greaterThan(0); + expect(Date.parse(match.updated_at)).to.be.greaterThan(0); break; default: throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); @@ -290,38 +292,38 @@ export default function createFindTests({ getService }: FtrProviderContext) { it('should handle find alert request with fields appropriately', async () => { const myTag = uuid.v4(); const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ enabled: false, tags: [myTag], - alertTypeId: 'test.restricted-noop', + rule_type_id: 'test.restricted-noop', consumer: 'alertsRestrictedFixture', }) ) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); // create another type with same tag const { body: createdSecondAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ tags: [myTag], - alertTypeId: 'test.restricted-noop', + rule_type_id: 'test.restricted-noop', consumer: 'alertsRestrictedFixture', }) ) .expect(200); - objectRemover.add(space.id, createdSecondAlert.id, 'alert', 'alerts'); + objectRemover.add(space.id, createdSecondAlert.id, 'rule', 'alerting'); const response = await supertestWithoutAuth .get( `${getUrlPrefix( space.id - )}/api/alerts/_find?filter=alert.attributes.alertTypeId:test.restricted-noop&fields=["tags"]&sort_field=createdAt` + )}/api/alerting/rules/_find?filter=alert.attributes.alertTypeId:test.restricted-noop&fields=["tags"]&sort_field=createdAt` ) .auth(user.username, user.password); @@ -345,7 +347,7 @@ export default function createFindTests({ getService }: FtrProviderContext) { case 'space_1_all_with_restricted_fixture at space1': expect(response.statusCode).to.eql(200); expect(response.body.page).to.equal(1); - expect(response.body.perPage).to.be.greaterThan(0); + expect(response.body.per_page).to.be.greaterThan(0); expect(response.body.total).to.be.greaterThan(0); const [matchFirst, matchSecond] = response.body.data; expect(omit(matchFirst, 'updatedAt')).to.eql({ @@ -367,38 +369,38 @@ export default function createFindTests({ getService }: FtrProviderContext) { it('should handle find alert request with executionStatus field appropriately', async () => { const myTag = uuid.v4(); const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ enabled: false, tags: [myTag], - alertTypeId: 'test.restricted-noop', + rule_type_id: 'test.restricted-noop', consumer: 'alertsRestrictedFixture', }) ) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); // create another type with same tag const { body: createdSecondAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ tags: [myTag], - alertTypeId: 'test.restricted-noop', + rule_type_id: 'test.restricted-noop', consumer: 'alertsRestrictedFixture', }) ) .expect(200); - objectRemover.add(space.id, createdSecondAlert.id, 'alert', 'alerts'); + objectRemover.add(space.id, createdSecondAlert.id, 'rule', 'alerting'); const response = await supertestWithoutAuth .get( `${getUrlPrefix( space.id - )}/api/alerts/_find?filter=alert.attributes.alertTypeId:test.restricted-noop&fields=["tags","executionStatus"]&sort_field=createdAt` + )}/api/alerting/rules/_find?filter=alert.attributes.alertTypeId:test.restricted-noop&fields=["tags","executionStatus"]&sort_field=createdAt` ) .auth(user.username, user.password); @@ -422,20 +424,20 @@ export default function createFindTests({ getService }: FtrProviderContext) { case 'space_1_all_with_restricted_fixture at space1': expect(response.statusCode).to.eql(200); expect(response.body.page).to.equal(1); - expect(response.body.perPage).to.be.greaterThan(0); + expect(response.body.per_page).to.be.greaterThan(0); expect(response.body.total).to.be.greaterThan(0); const [matchFirst, matchSecond] = response.body.data; expect(omit(matchFirst, 'updatedAt')).to.eql({ id: createdAlert.id, actions: [], tags: [myTag], - executionStatus: matchFirst.executionStatus, + execution_status: matchFirst.execution_status, }); expect(omit(matchSecond, 'updatedAt')).to.eql({ id: createdSecondAlert.id, actions: [], tags: [myTag], - executionStatus: matchSecond.executionStatus, + execution_status: matchSecond.execution_status, }); break; default: @@ -445,15 +447,17 @@ export default function createFindTests({ getService }: FtrProviderContext) { it(`shouldn't find alert from another space`, async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); const response = await supertestWithoutAuth .get( - `${getUrlPrefix('other')}/api/alerts/_find?search=test.noop&search_fields=alertTypeId` + `${getUrlPrefix( + 'other' + )}/api/alerting/rules/_find?search=test.noop&search_fields=alertTypeId` ) .auth(user.username, user.password); @@ -475,7 +479,7 @@ export default function createFindTests({ getService }: FtrProviderContext) { expect(response.statusCode).to.eql(200); expect(response.body).to.eql({ page: 1, - perPage: 10, + per_page: 10, total: 0, data: [], }); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/get.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/get.ts index 1277084b9eb0db..0400557209348c 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/get.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/get.ts @@ -31,14 +31,14 @@ export default function createGetTests({ getService }: FtrProviderContext) { describe(scenario.id, () => { it('should handle get alert request appropriately', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); const response = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) + .get(`${getUrlPrefix(space.id)}/api/alerting/rule/${createdAlert.id}`) .auth(user.username, user.password); switch (scenario.id) { @@ -61,26 +61,26 @@ export default function createGetTests({ getService }: FtrProviderContext) { id: createdAlert.id, name: 'abc', tags: ['foo'], - alertTypeId: 'test.noop', + rule_type_id: 'test.noop', consumer: 'alertsFixture', schedule: { interval: '1m' }, enabled: true, actions: [], params: {}, - createdBy: 'elastic', - scheduledTaskId: response.body.scheduledTaskId, - updatedAt: response.body.updatedAt, - createdAt: response.body.createdAt, + created_by: 'elastic', + scheduled_task_id: response.body.scheduled_task_id, + updated_at: response.body.updated_at, + created_at: response.body.created_at, throttle: '1m', - notifyWhen: 'onThrottleInterval', - updatedBy: 'elastic', - apiKeyOwner: 'elastic', - muteAll: false, - mutedInstanceIds: [], - executionStatus: response.body.executionStatus, + notify_when: 'onThrottleInterval', + updated_by: 'elastic', + api_key_owner: 'elastic', + mute_all: false, + muted_alert_ids: [], + execution_status: response.body.execution_status, }); - expect(Date.parse(response.body.createdAt)).to.be.greaterThan(0); - expect(Date.parse(response.body.updatedAt)).to.be.greaterThan(0); + expect(Date.parse(response.body.created_at)).to.be.greaterThan(0); + expect(Date.parse(response.body.updated_at)).to.be.greaterThan(0); break; default: throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); @@ -89,19 +89,19 @@ export default function createGetTests({ getService }: FtrProviderContext) { it('should handle get alert request appropriately when consumer is the same as producer', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ - alertTypeId: 'test.restricted-noop', + rule_type_id: 'test.restricted-noop', consumer: 'alertsRestrictedFixture', }) ) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); const response = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) + .get(`${getUrlPrefix(space.id)}/api/alerting/rule/${createdAlert.id}`) .auth(user.username, user.password); switch (scenario.id) { @@ -132,19 +132,19 @@ export default function createGetTests({ getService }: FtrProviderContext) { it('should handle get alert request appropriately when consumer is not the producer', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ - alertTypeId: 'test.unrestricted-noop', + rule_type_id: 'test.unrestricted-noop', consumer: 'alertsFixture', }) ) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); const response = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) + .get(`${getUrlPrefix(space.id)}/api/alerting/rule/${createdAlert.id}`) .auth(user.username, user.password); switch (scenario.id) { @@ -186,19 +186,19 @@ export default function createGetTests({ getService }: FtrProviderContext) { it('should handle get alert request appropriately when consumer is "alerts"', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ - alertTypeId: 'test.restricted-noop', + rule_type_id: 'test.restricted-noop', consumer: 'alerts', }) ) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); const response = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) + .get(`${getUrlPrefix(space.id)}/api/alerting/rule/${createdAlert.id}`) .auth(user.username, user.password); switch (scenario.id) { @@ -240,14 +240,14 @@ export default function createGetTests({ getService }: FtrProviderContext) { it(`shouldn't get alert from another space`, async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); const response = await supertestWithoutAuth - .get(`${getUrlPrefix('other')}/api/alerts/alert/${createdAlert.id}`) + .get(`${getUrlPrefix('other')}/api/alerting/rule/${createdAlert.id}`) .auth(user.username, user.password); expect(response.statusCode).to.eql(404); @@ -272,7 +272,7 @@ export default function createGetTests({ getService }: FtrProviderContext) { it(`should handle get alert request appropriately when alert doesn't exist`, async () => { const response = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/alerts/alert/1`) + .get(`${getUrlPrefix(space.id)}/api/alerting/rule/1`) .auth(user.username, user.password); switch (scenario.id) { diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/get_alert_instance_summary.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/get_alert_instance_summary.ts index 11514a8cf630c3..d4ca6c2aa9cd89 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/get_alert_instance_summary.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/get_alert_instance_summary.ts @@ -32,14 +32,16 @@ export default function createGetAlertInstanceSummaryTests({ getService }: FtrPr describe(scenario.id, () => { it('should handle getAlertInstanceSummary alert request appropriately', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); const response = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}/_instance_summary`) + .get( + `${getUrlPrefix(space.id)}/internal/alerting/rule/${createdAlert.id}/_alert_summary` + ) .auth(user.username, user.password); switch (scenario.id) { @@ -58,27 +60,31 @@ export default function createGetAlertInstanceSummaryTests({ getService }: FtrPr case 'space_1_all_alerts_none_actions at space1': case 'space_1_all_with_restricted_fixture at space1': expect(response.statusCode).to.eql(200); - const { id, statusStartDate, statusEndDate } = response.body; + const { + id, + status_start_date: statusStartDate, + status_end_date: statusEndDate, + } = response.body; expect(id).to.equal(createdAlert.id); expect(Date.parse(statusStartDate)).to.be.lessThan(Date.parse(statusEndDate)); const stableBody = omit(response.body, [ 'id', - 'statusStartDate', - 'statusEndDate', - 'lastRun', + 'status_start_date', + 'status_end_date', + 'last_run', ]); expect(stableBody).to.eql({ name: 'abc', tags: ['foo'], - alertTypeId: 'test.noop', + rule_type_id: 'test.noop', consumer: 'alertsFixture', status: 'OK', - muteAll: false, + mute_all: false, throttle: '1m', enabled: true, - errorMessages: [], - instances: {}, + error_messages: [], + alerts: {}, }); break; default: @@ -88,19 +94,21 @@ export default function createGetAlertInstanceSummaryTests({ getService }: FtrPr it('should handle getAlertInstanceSummary alert request appropriately when unauthorized', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ - alertTypeId: 'test.unrestricted-noop', + rule_type_id: 'test.unrestricted-noop', consumer: 'alertsFixture', }) ) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); const response = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}/_instance_summary`) + .get( + `${getUrlPrefix(space.id)}/internal/alerting/rule/${createdAlert.id}/_alert_summary` + ) .auth(user.username, user.password); switch (scenario.id) { @@ -134,7 +142,7 @@ export default function createGetAlertInstanceSummaryTests({ getService }: FtrPr case 'superuser at space1': case 'space_1_all_with_restricted_fixture at space1': expect(response.statusCode).to.eql(200); - expect(response.body).to.key('id', 'instances', 'errorMessages'); + expect(response.body).to.key('id', 'alerts', 'error_messages'); break; default: throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); @@ -143,14 +151,16 @@ export default function createGetAlertInstanceSummaryTests({ getService }: FtrPr it(`shouldn't getAlertInstanceSummary for an alert from another space`, async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); const response = await supertestWithoutAuth - .get(`${getUrlPrefix('other')}/api/alerts/alert/${createdAlert.id}/_instance_summary`) + .get( + `${getUrlPrefix('other')}/internal/alerting/rule/${createdAlert.id}/_alert_summary` + ) .auth(user.username, user.password); expect(response.statusCode).to.eql(404); @@ -175,7 +185,7 @@ export default function createGetAlertInstanceSummaryTests({ getService }: FtrPr it(`should handle getAlertInstanceSummary request appropriately when alert doesn't exist`, async () => { const response = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/alerts/alert/1/_instance_summary`) + .get(`${getUrlPrefix(space.id)}/internal/alerting/rule/1/_alert_summary`) .auth(user.username, user.password); switch (scenario.id) { diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/get_alert_state.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/get_alert_state.ts index 5dcecd3f1b05f1..e00d8e53e438eb 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/get_alert_state.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/get_alert_state.ts @@ -31,14 +31,14 @@ export default function createGetAlertStateTests({ getService }: FtrProviderCont describe(scenario.id, () => { it('should handle getAlertState alert request appropriately', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); const response = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}/state`) + .get(`${getUrlPrefix(space.id)}/internal/alerting/rule/${createdAlert.id}/state`) .auth(user.username, user.password); switch (scenario.id) { @@ -57,7 +57,7 @@ export default function createGetAlertStateTests({ getService }: FtrProviderCont case 'space_1_all_alerts_none_actions at space1': case 'space_1_all_with_restricted_fixture at space1': expect(response.statusCode).to.eql(200); - expect(response.body).to.key('alertInstances', 'previousStartedAt'); + expect(response.body).to.key('alerts', 'previous_started_at'); break; default: throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); @@ -66,19 +66,19 @@ export default function createGetAlertStateTests({ getService }: FtrProviderCont it('should handle getAlertState alert request appropriately when unauthorized', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ - alertTypeId: 'test.unrestricted-noop', + rule_type_id: 'test.unrestricted-noop', consumer: 'alertsFixture', }) ) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); const response = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}/state`) + .get(`${getUrlPrefix(space.id)}/internal/alerting/rule/${createdAlert.id}/state`) .auth(user.username, user.password); switch (scenario.id) { @@ -112,7 +112,7 @@ export default function createGetAlertStateTests({ getService }: FtrProviderCont case 'superuser at space1': case 'space_1_all_with_restricted_fixture at space1': expect(response.statusCode).to.eql(200); - expect(response.body).to.key('alertInstances', 'previousStartedAt'); + expect(response.body).to.key('alerts', 'previous_started_at'); break; default: throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); @@ -121,14 +121,14 @@ export default function createGetAlertStateTests({ getService }: FtrProviderCont it(`shouldn't getAlertState for an alert from another space`, async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); const response = await supertestWithoutAuth - .get(`${getUrlPrefix('other')}/api/alerts/alert/${createdAlert.id}/state`) + .get(`${getUrlPrefix('other')}/internal/alerting/rule/${createdAlert.id}/state`) .auth(user.username, user.password); expect(response.statusCode).to.eql(404); @@ -153,7 +153,7 @@ export default function createGetAlertStateTests({ getService }: FtrProviderCont it(`should handle getAlertState request appropriately when alert doesn't exist`, async () => { const response = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/alerts/alert/1/state`) + .get(`${getUrlPrefix(space.id)}/internal/alerting/rule/1/state`) .auth(user.username, user.password); switch (scenario.id) { diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/index.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/index.ts index e8cc8ea699e17f..b1b52d89997cd3 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/index.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/index.ts @@ -41,7 +41,7 @@ export default function alertingTests({ loadTestFile, getService }: FtrProviderC loadTestFile(require.resolve('./get')); loadTestFile(require.resolve('./get_alert_state')); loadTestFile(require.resolve('./get_alert_instance_summary')); - loadTestFile(require.resolve('./list_alert_types')); + loadTestFile(require.resolve('./rule_types')); loadTestFile(require.resolve('./mute_all')); loadTestFile(require.resolve('./mute_instance')); loadTestFile(require.resolve('./unmute_all')); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/mustache_templates.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/mustache_templates.ts index 6ac50d84857281..8344d4a281ba1f 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/mustache_templates.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/mustache_templates.ts @@ -62,28 +62,28 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon it('should render kibanaBaseUrl as non-empty string since configured', async () => { const actionResponse = await supertest - .post(`${getUrlPrefix(Spaces[0].id)}/api/actions/action`) + .post(`${getUrlPrefix(Spaces[0].id)}/api/actions/connector`) .set('kbn-xsrf', 'test') .send({ name: 'testing context variable expansion', - actionTypeId: '.slack', + connector_type_id: '.slack', secrets: { webhookUrl: slackSimulatorURL, }, }); expect(actionResponse.status).to.eql(200); const createdAction = actionResponse.body; - objectRemover.add(Spaces[0].id, createdAction.id, 'action', 'actions'); + objectRemover.add(Spaces[0].id, createdAction.id, 'connector', 'actions'); const varsTemplate = 'kibanaBaseUrl: "{{kibanaBaseUrl}}"'; const alertResponse = await supertest - .post(`${getUrlPrefix(Spaces[0].id)}/api/alerts/alert`) + .post(`${getUrlPrefix(Spaces[0].id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ name: 'testing context variable kibanaBaseUrl', - alertTypeId: 'test.patternFiring', + rule_type_id: 'test.patternFiring', params: { pattern: { instance: [true, true] }, }, @@ -100,7 +100,7 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon ); expect(alertResponse.status).to.eql(200); const createdAlert = alertResponse.body; - objectRemover.add(Spaces[0].id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(Spaces[0].id, createdAlert.id, 'rule', 'alerting'); const body = await retry.try(async () => waitForActionBody(slackSimulatorURL, createdAlert.id) diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/mute_all.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/mute_all.ts index f226537e462f11..993b66353756f6 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/mute_all.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/mute_all.ts @@ -35,18 +35,18 @@ export default function createMuteAlertTests({ getService }: FtrProviderContext) describe(scenario.id, () => { it('should handle mute alert request appropriately', async () => { const { body: createdAction } = await supertest - .post(`${getUrlPrefix(space.id)}/api/actions/action`) + .post(`${getUrlPrefix(space.id)}/api/actions/connector`) .set('kbn-xsrf', 'foo') .send({ name: 'MY action', - actionTypeId: 'test.noop', + connector_type_id: 'test.noop', config: {}, secrets: {}, }) .expect(200); const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ @@ -61,7 +61,7 @@ export default function createMuteAlertTests({ getService }: FtrProviderContext) }) ) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); const response = await alertUtils.getMuteAllRequest(createdAlert.id); @@ -94,11 +94,11 @@ export default function createMuteAlertTests({ getService }: FtrProviderContext) expect(response.statusCode).to.eql(204); expect(response.body).to.eql(''); const { body: updatedAlert } = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) + .get(`${getUrlPrefix(space.id)}/api/alerting/rule/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .expect(200); - expect(updatedAlert.muteAll).to.eql(true); + expect(updatedAlert.mute_all).to.eql(true); // Ensure AAD isn't broken await checkAAD({ supertest, @@ -114,17 +114,17 @@ export default function createMuteAlertTests({ getService }: FtrProviderContext) it('should handle mute alert request appropriately when consumer is the same as producer', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ enabled: false, - alertTypeId: 'test.restricted-noop', + rule_type_id: 'test.restricted-noop', consumer: 'alertsRestrictedFixture', }) ) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); const response = await alertUtils.getMuteAllRequest(createdAlert.id); @@ -150,11 +150,11 @@ export default function createMuteAlertTests({ getService }: FtrProviderContext) expect(response.statusCode).to.eql(204); expect(response.body).to.eql(''); const { body: updatedAlert } = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) + .get(`${getUrlPrefix(space.id)}/api/alerting/rule/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .expect(200); - expect(updatedAlert.muteAll).to.eql(true); + expect(updatedAlert.mute_all).to.eql(true); // Ensure AAD isn't broken await checkAAD({ supertest, @@ -170,17 +170,17 @@ export default function createMuteAlertTests({ getService }: FtrProviderContext) it('should handle mute alert request appropriately when consumer is not the producer', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ enabled: false, - alertTypeId: 'test.unrestricted-noop', + rule_type_id: 'test.unrestricted-noop', consumer: 'alertsFixture', }) ) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); const response = await alertUtils.getMuteAllRequest(createdAlert.id); @@ -217,11 +217,11 @@ export default function createMuteAlertTests({ getService }: FtrProviderContext) expect(response.statusCode).to.eql(204); expect(response.body).to.eql(''); const { body: updatedAlert } = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) + .get(`${getUrlPrefix(space.id)}/api/alerting/rule/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .expect(200); - expect(updatedAlert.muteAll).to.eql(true); + expect(updatedAlert.mute_all).to.eql(true); // Ensure AAD isn't broken await checkAAD({ supertest, @@ -237,17 +237,17 @@ export default function createMuteAlertTests({ getService }: FtrProviderContext) it('should handle mute alert request appropriately when consumer is "alerts"', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ enabled: false, - alertTypeId: 'test.restricted-noop', + rule_type_id: 'test.restricted-noop', consumer: 'alerts', }) ) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); const response = await alertUtils.getMuteAllRequest(createdAlert.id); @@ -284,11 +284,11 @@ export default function createMuteAlertTests({ getService }: FtrProviderContext) expect(response.statusCode).to.eql(204); expect(response.body).to.eql(''); const { body: updatedAlert } = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) + .get(`${getUrlPrefix(space.id)}/api/alerting/rule/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .expect(200); - expect(updatedAlert.muteAll).to.eql(true); + expect(updatedAlert.mute_all).to.eql(true); // Ensure AAD isn't broken await checkAAD({ supertest, diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/mute_instance.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/mute_instance.ts index f70a2a1af5429b..858e61154cef59 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/mute_instance.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/mute_instance.ts @@ -35,18 +35,18 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider describe(scenario.id, () => { it('should handle mute alert instance request appropriately', async () => { const { body: createdAction } = await supertest - .post(`${getUrlPrefix(space.id)}/api/actions/action`) + .post(`${getUrlPrefix(space.id)}/api/actions/connector`) .set('kbn-xsrf', 'foo') .send({ name: 'MY action', - actionTypeId: 'test.noop', + connector_type_id: 'test.noop', config: {}, secrets: {}, }) .expect(200); const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ @@ -61,7 +61,7 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider }) ) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); const response = await alertUtils.getMuteInstanceRequest(createdAlert.id, '1'); @@ -94,11 +94,11 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider expect(response.statusCode).to.eql(204); expect(response.body).to.eql(''); const { body: updatedAlert } = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) + .get(`${getUrlPrefix(space.id)}/api/alerting/rule/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .expect(200); - expect(updatedAlert.mutedInstanceIds).to.eql(['1']); + expect(updatedAlert.muted_alert_ids).to.eql(['1']); // Ensure AAD isn't broken await checkAAD({ supertest, @@ -114,17 +114,17 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider it('should handle mute alert instance request appropriately when consumer is the same as producer', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ enabled: false, - alertTypeId: 'test.restricted-noop', + rule_type_id: 'test.restricted-noop', consumer: 'alertsRestrictedFixture', }) ) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); const response = await alertUtils.getMuteInstanceRequest(createdAlert.id, '1'); @@ -150,11 +150,11 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider expect(response.statusCode).to.eql(204); expect(response.body).to.eql(''); const { body: updatedAlert } = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) + .get(`${getUrlPrefix(space.id)}/api/alerting/rule/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .expect(200); - expect(updatedAlert.mutedInstanceIds).to.eql(['1']); + expect(updatedAlert.muted_alert_ids).to.eql(['1']); // Ensure AAD isn't broken await checkAAD({ supertest, @@ -170,17 +170,17 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider it('should handle mute alert instance request appropriately when consumer is not the producer', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ enabled: false, - alertTypeId: 'test.unrestricted-noop', + rule_type_id: 'test.unrestricted-noop', consumer: 'alertsFixture', }) ) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); const response = await alertUtils.getMuteInstanceRequest(createdAlert.id, '1'); @@ -217,11 +217,11 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider expect(response.statusCode).to.eql(204); expect(response.body).to.eql(''); const { body: updatedAlert } = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) + .get(`${getUrlPrefix(space.id)}/api/alerting/rule/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .expect(200); - expect(updatedAlert.mutedInstanceIds).to.eql(['1']); + expect(updatedAlert.muted_alert_ids).to.eql(['1']); // Ensure AAD isn't broken await checkAAD({ supertest, @@ -237,17 +237,17 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider it('should handle mute alert instance request appropriately when consumer is "alerts"', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ enabled: false, - alertTypeId: 'test.restricted-noop', + rule_type_id: 'test.restricted-noop', consumer: 'alerts', }) ) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); const response = await alertUtils.getMuteInstanceRequest(createdAlert.id, '1'); @@ -284,11 +284,11 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider expect(response.statusCode).to.eql(204); expect(response.body).to.eql(''); const { body: updatedAlert } = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) + .get(`${getUrlPrefix(space.id)}/api/alerting/rule/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .expect(200); - expect(updatedAlert.mutedInstanceIds).to.eql(['1']); + expect(updatedAlert.muted_alert_ids).to.eql(['1']); // Ensure AAD isn't broken await checkAAD({ supertest, @@ -304,16 +304,14 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider it('should handle mute alert instance request appropriately and not duplicate mutedInstanceIds when muting an instance already muted', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send(getTestAlertData({ enabled: false })) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); await supertest - .post( - `${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}/alert_instance/1/_mute` - ) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule/${createdAlert.id}/alert/1/_mute`) .set('kbn-xsrf', 'foo') .expect(204, ''); @@ -341,11 +339,11 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider expect(response.statusCode).to.eql(204); expect(response.body).to.eql(''); const { body: updatedAlert } = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) + .get(`${getUrlPrefix(space.id)}/api/alerting/rule/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .expect(200); - expect(updatedAlert.mutedInstanceIds).to.eql(['1']); + expect(updatedAlert.muted_alert_ids).to.eql(['1']); break; default: throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/rbac_legacy.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/rbac_legacy.ts index 3db35653747400..fb32be12500cad 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/rbac_legacy.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/rbac_legacy.ts @@ -160,7 +160,7 @@ export default function alertTests({ getService }: FtrProviderContext) { async function ensureLegacyAlertHasBeenMigrated(alertId: string) { const getResponse = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/alerts/alert/${alertId}`) + .get(`${getUrlPrefix(space.id)}/api/alerting/rule/${alertId}`) .auth(user.username, user.password); expect(getResponse.status).to.eql(200); } diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/list_alert_types.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/rule_types.ts similarity index 66% rename from x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/list_alert_types.ts rename to x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/rule_types.ts index 1da93e2d850a3b..c851aaf2bbc88d 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/list_alert_types.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/rule_types.ts @@ -16,56 +16,56 @@ export default function listAlertTypes({ getService }: FtrProviderContext) { const supertestWithoutAuth = getService('supertestWithoutAuth'); const expectedNoOpType = { - actionGroups: [ + action_groups: [ { id: 'default', name: 'Default' }, { id: 'recovered', name: 'Recovered' }, ], - defaultActionGroupId: 'default', + default_action_group_id: 'default', id: 'test.noop', name: 'Test: Noop', - actionVariables: { + action_variables: { state: [], context: [], params: [], }, producer: 'alertsFixture', - minimumLicenseRequired: 'basic', - recoveryActionGroup: { + minimum_license_required: 'basic', + recovery_action_group: { id: 'recovered', name: 'Recovered', }, - enabledInLicense: true, + enabled_in_license: true, }; const expectedRestrictedNoOpType = { - actionGroups: [ + action_groups: [ { id: 'default', name: 'Default' }, { id: 'restrictedRecovered', name: 'Restricted Recovery' }, ], - recoveryActionGroup: { + recovery_action_group: { id: 'restrictedRecovered', name: 'Restricted Recovery', }, - defaultActionGroupId: 'default', + default_action_group_id: 'default', id: 'test.restricted-noop', name: 'Test: Restricted Noop', - actionVariables: { + action_variables: { state: [], context: [], params: [], }, producer: 'alertsRestrictedFixture', - minimumLicenseRequired: 'basic', - enabledInLicense: true, + minimum_license_required: 'basic', + enabled_in_license: true, }; - describe('list_alert_types', () => { + describe('rule_types', () => { for (const scenario of UserAtSpaceScenarios) { const { user, space } = scenario; describe(scenario.id, () => { it('should return 200 with list of globally available alert types', async () => { const response = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/alerts/list_alert_types`) + .get(`${getUrlPrefix(space.id)}/api/alerting/rule_types`) .auth(user.username, user.password); expect(response.statusCode).to.eql(200); @@ -82,76 +82,76 @@ export default function listAlertTypes({ getService }: FtrProviderContext) { break; case 'space_1_all at space1': case 'space_1_all_alerts_none_actions at space1': - expect(omit(noOpAlertType, 'authorizedConsumers')).to.eql(expectedNoOpType); + expect(omit(noOpAlertType, 'authorized_consumers')).to.eql(expectedNoOpType); expect(restrictedNoOpAlertType).to.eql(undefined); - expect(noOpAlertType.authorizedConsumers).to.eql({ + expect(noOpAlertType.authorized_consumers).to.eql({ alerts: { read: true, all: true }, alertsFixture: { read: true, all: true }, }); break; case 'global_read at space1': - expect(omit(noOpAlertType, 'authorizedConsumers')).to.eql(expectedNoOpType); - expect(noOpAlertType.authorizedConsumers.alertsFixture).to.eql({ + expect(omit(noOpAlertType, 'authorized_consumers')).to.eql(expectedNoOpType); + expect(noOpAlertType.authorized_consumers.alertsFixture).to.eql({ read: true, all: false, }); - expect(noOpAlertType.authorizedConsumers.alertsRestrictedFixture).to.eql({ + expect(noOpAlertType.authorized_consumers.alertsRestrictedFixture).to.eql({ read: true, all: false, }); - expect(omit(restrictedNoOpAlertType, 'authorizedConsumers')).to.eql( + expect(omit(restrictedNoOpAlertType, 'authorized_consumers')).to.eql( expectedRestrictedNoOpType ); - expect(Object.keys(restrictedNoOpAlertType.authorizedConsumers)).not.to.contain( + expect(Object.keys(restrictedNoOpAlertType.authorized_consumers)).not.to.contain( 'alertsFixture' ); - expect(restrictedNoOpAlertType.authorizedConsumers.alertsRestrictedFixture).to.eql({ + expect(restrictedNoOpAlertType.authorized_consumers.alertsRestrictedFixture).to.eql({ read: true, all: false, }); break; case 'space_1_all_with_restricted_fixture at space1': - expect(omit(noOpAlertType, 'authorizedConsumers')).to.eql(expectedNoOpType); - expect(noOpAlertType.authorizedConsumers.alertsFixture).to.eql({ + expect(omit(noOpAlertType, 'authorized_consumers')).to.eql(expectedNoOpType); + expect(noOpAlertType.authorized_consumers.alertsFixture).to.eql({ read: true, all: true, }); - expect(noOpAlertType.authorizedConsumers.alertsRestrictedFixture).to.eql({ + expect(noOpAlertType.authorized_consumers.alertsRestrictedFixture).to.eql({ read: true, all: true, }); - expect(omit(restrictedNoOpAlertType, 'authorizedConsumers')).to.eql( + expect(omit(restrictedNoOpAlertType, 'authorized_consumers')).to.eql( expectedRestrictedNoOpType ); - expect(Object.keys(restrictedNoOpAlertType.authorizedConsumers)).not.to.contain( + expect(Object.keys(restrictedNoOpAlertType.authorized_consumers)).not.to.contain( 'alertsFixture' ); - expect(restrictedNoOpAlertType.authorizedConsumers.alertsRestrictedFixture).to.eql({ + expect(restrictedNoOpAlertType.authorized_consumers.alertsRestrictedFixture).to.eql({ read: true, all: true, }); break; case 'superuser at space1': - expect(omit(noOpAlertType, 'authorizedConsumers')).to.eql(expectedNoOpType); - expect(noOpAlertType.authorizedConsumers.alertsFixture).to.eql({ + expect(omit(noOpAlertType, 'authorized_consumers')).to.eql(expectedNoOpType); + expect(noOpAlertType.authorized_consumers.alertsFixture).to.eql({ read: true, all: true, }); - expect(noOpAlertType.authorizedConsumers.alertsRestrictedFixture).to.eql({ + expect(noOpAlertType.authorized_consumers.alertsRestrictedFixture).to.eql({ read: true, all: true, }); - expect(omit(restrictedNoOpAlertType, 'authorizedConsumers')).to.eql( + expect(omit(restrictedNoOpAlertType, 'authorized_consumers')).to.eql( expectedRestrictedNoOpType ); - expect(noOpAlertType.authorizedConsumers.alertsFixture).to.eql({ + expect(noOpAlertType.authorized_consumers.alertsFixture).to.eql({ read: true, all: true, }); - expect(noOpAlertType.authorizedConsumers.alertsRestrictedFixture).to.eql({ + expect(noOpAlertType.authorized_consumers.alertsRestrictedFixture).to.eql({ read: true, all: true, }); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/unmute_all.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/unmute_all.ts index 885759359f2292..526f809033646c 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/unmute_all.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/unmute_all.ts @@ -35,18 +35,18 @@ export default function createUnmuteAlertTests({ getService }: FtrProviderContex describe(scenario.id, () => { it('should handle unmute alert request appropriately', async () => { const { body: createdAction } = await supertest - .post(`${getUrlPrefix(space.id)}/api/actions/action`) + .post(`${getUrlPrefix(space.id)}/api/actions/connector`) .set('kbn-xsrf', 'foo') .send({ name: 'MY action', - actionTypeId: 'test.noop', + connector_type_id: 'test.noop', config: {}, secrets: {}, }) .expect(200); const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ @@ -61,10 +61,10 @@ export default function createUnmuteAlertTests({ getService }: FtrProviderContex }) ) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}/_mute_all`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule/${createdAlert.id}/_mute_all`) .set('kbn-xsrf', 'foo') .expect(204, ''); @@ -99,11 +99,11 @@ export default function createUnmuteAlertTests({ getService }: FtrProviderContex expect(response.statusCode).to.eql(204); expect(response.body).to.eql(''); const { body: updatedAlert } = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) + .get(`${getUrlPrefix(space.id)}/api/alerting/rule/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .expect(200); - expect(updatedAlert.muteAll).to.eql(false); + expect(updatedAlert.mute_all).to.eql(false); // Ensure AAD isn't broken await checkAAD({ supertest, @@ -119,20 +119,20 @@ export default function createUnmuteAlertTests({ getService }: FtrProviderContex it('should handle unmute alert request appropriately when consumer is the same as producer', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ enabled: false, - alertTypeId: 'test.restricted-noop', + rule_type_id: 'test.restricted-noop', consumer: 'alertsRestrictedFixture', }) ) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}/_mute_all`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule/${createdAlert.id}/_mute_all`) .set('kbn-xsrf', 'foo') .expect(204, ''); @@ -160,11 +160,11 @@ export default function createUnmuteAlertTests({ getService }: FtrProviderContex expect(response.statusCode).to.eql(204); expect(response.body).to.eql(''); const { body: updatedAlert } = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) + .get(`${getUrlPrefix(space.id)}/api/alerting/rule/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .expect(200); - expect(updatedAlert.muteAll).to.eql(false); + expect(updatedAlert.mute_all).to.eql(false); // Ensure AAD isn't broken await checkAAD({ supertest, @@ -180,20 +180,20 @@ export default function createUnmuteAlertTests({ getService }: FtrProviderContex it('should handle unmute alert request appropriately when consumer is not the producer', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ enabled: false, - alertTypeId: 'test.unrestricted-noop', + rule_type_id: 'test.unrestricted-noop', consumer: 'alertsFixture', }) ) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}/_mute_all`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule/${createdAlert.id}/_mute_all`) .set('kbn-xsrf', 'foo') .expect(204, ''); @@ -232,11 +232,11 @@ export default function createUnmuteAlertTests({ getService }: FtrProviderContex expect(response.statusCode).to.eql(204); expect(response.body).to.eql(''); const { body: updatedAlert } = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) + .get(`${getUrlPrefix(space.id)}/api/alerting/rule/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .expect(200); - expect(updatedAlert.muteAll).to.eql(false); + expect(updatedAlert.mute_all).to.eql(false); // Ensure AAD isn't broken await checkAAD({ supertest, @@ -252,20 +252,20 @@ export default function createUnmuteAlertTests({ getService }: FtrProviderContex it('should handle unmute alert request appropriately when consumer is "alerts"', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ enabled: false, - alertTypeId: 'test.restricted-noop', + rule_type_id: 'test.restricted-noop', consumer: 'alerts', }) ) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}/_mute_all`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule/${createdAlert.id}/_mute_all`) .set('kbn-xsrf', 'foo') .expect(204, ''); @@ -304,11 +304,11 @@ export default function createUnmuteAlertTests({ getService }: FtrProviderContex expect(response.statusCode).to.eql(204); expect(response.body).to.eql(''); const { body: updatedAlert } = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) + .get(`${getUrlPrefix(space.id)}/api/alerting/rule/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .expect(200); - expect(updatedAlert.muteAll).to.eql(false); + expect(updatedAlert.mute_all).to.eql(false); // Ensure AAD isn't broken await checkAAD({ supertest, diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/unmute_instance.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/unmute_instance.ts index fed008468e735a..c19da8ba2008c5 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/unmute_instance.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/unmute_instance.ts @@ -35,18 +35,18 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider describe(scenario.id, () => { it('should handle unmute alert instance request appropriately', async () => { const { body: createdAction } = await supertest - .post(`${getUrlPrefix(space.id)}/api/actions/action`) + .post(`${getUrlPrefix(space.id)}/api/actions/connector`) .set('kbn-xsrf', 'foo') .send({ name: 'MY action', - actionTypeId: 'test.noop', + connector_type_id: 'test.noop', config: {}, secrets: {}, }) .expect(200); const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ @@ -61,12 +61,10 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider }) ) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); await supertest - .post( - `${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}/alert_instance/1/_mute` - ) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule/${createdAlert.id}/alert/1/_mute`) .set('kbn-xsrf', 'foo') .expect(204, ''); @@ -101,11 +99,11 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider expect(response.statusCode).to.eql(204); expect(response.body).to.eql(''); const { body: updatedAlert } = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) + .get(`${getUrlPrefix(space.id)}/api/alerting/rule/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .expect(200); - expect(updatedAlert.mutedInstanceIds).to.eql([]); + expect(updatedAlert.muted_alert_ids).to.eql([]); // Ensure AAD isn't broken await checkAAD({ supertest, @@ -121,22 +119,20 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider it('should handle unmute alert instance request appropriately when consumer is the same as producer', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ enabled: false, - alertTypeId: 'test.restricted-noop', + rule_type_id: 'test.restricted-noop', consumer: 'alertsRestrictedFixture', }) ) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); await supertest - .post( - `${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}/alert_instance/1/_mute` - ) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule/${createdAlert.id}/alert/1/_mute`) .set('kbn-xsrf', 'foo') .expect(204, ''); @@ -164,11 +160,11 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider expect(response.statusCode).to.eql(204); expect(response.body).to.eql(''); const { body: updatedAlert } = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) + .get(`${getUrlPrefix(space.id)}/api/alerting/rule/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .expect(200); - expect(updatedAlert.mutedInstanceIds).to.eql([]); + expect(updatedAlert.muted_alert_ids).to.eql([]); // Ensure AAD isn't broken await checkAAD({ supertest, @@ -184,22 +180,20 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider it('should handle unmute alert instance request appropriately when consumer is not the producer', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ enabled: false, - alertTypeId: 'test.unrestricted-noop', + rule_type_id: 'test.unrestricted-noop', consumer: 'alertsFixture', }) ) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); await supertest - .post( - `${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}/alert_instance/1/_mute` - ) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule/${createdAlert.id}/alert/1/_mute`) .set('kbn-xsrf', 'foo') .expect(204, ''); @@ -238,11 +232,11 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider expect(response.statusCode).to.eql(204); expect(response.body).to.eql(''); const { body: updatedAlert } = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) + .get(`${getUrlPrefix(space.id)}/api/alerting/rule/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .expect(200); - expect(updatedAlert.mutedInstanceIds).to.eql([]); + expect(updatedAlert.muted_alert_ids).to.eql([]); // Ensure AAD isn't broken await checkAAD({ supertest, @@ -258,22 +252,20 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider it('should handle unmute alert instance request appropriately when consumer is "alerts"', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ enabled: false, - alertTypeId: 'test.restricted-noop', + rule_type_id: 'test.restricted-noop', consumer: 'alerts', }) ) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); await supertest - .post( - `${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}/alert_instance/1/_mute` - ) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule/${createdAlert.id}/alert/1/_mute`) .set('kbn-xsrf', 'foo') .expect(204, ''); @@ -312,11 +304,11 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider expect(response.statusCode).to.eql(204); expect(response.body).to.eql(''); const { body: updatedAlert } = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) + .get(`${getUrlPrefix(space.id)}/api/alerting/rule/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .expect(200); - expect(updatedAlert.mutedInstanceIds).to.eql([]); + expect(updatedAlert.muted_alert_ids).to.eql([]); // Ensure AAD isn't broken await checkAAD({ supertest, diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update.ts index 300a884d5a006a..e628f0b3d950e2 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update.ts @@ -42,22 +42,22 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { describe(scenario.id, () => { it('should handle update alert request appropriately', async () => { const { body: createdAction } = await supertest - .post(`${getUrlPrefix(space.id)}/api/actions/action`) + .post(`${getUrlPrefix(space.id)}/api/actions/connector`) .set('kbn-xsrf', 'foo') .send({ name: 'MY action', - actionTypeId: 'test.noop', + connector_type_id: 'test.noop', config: {}, secrets: {}, }) .expect(200); const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); const updatedData = { name: 'bcd', @@ -74,10 +74,10 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { }, ], throttle: '1m', - notifyWhen: 'onThrottleInterval', + notify_when: 'onThrottleInterval', }; const response = await supertestWithoutAuth - .put(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) + .put(`${getUrlPrefix(space.id)}/api/alerting/rule/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send(updatedData); @@ -112,31 +112,31 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { expect(response.body).to.eql({ ...updatedData, id: createdAlert.id, - alertTypeId: 'test.noop', + rule_type_id: 'test.noop', consumer: 'alertsFixture', - createdBy: 'elastic', + created_by: 'elastic', enabled: true, - updatedBy: user.username, - apiKeyOwner: user.username, - muteAll: false, - mutedInstanceIds: [], + updated_by: user.username, + api_key_owner: user.username, + mute_all: false, + muted_alert_ids: [], actions: [ { id: createdAction.id, - actionTypeId: 'test.noop', + connector_type_id: 'test.noop', group: 'default', params: {}, }, ], - scheduledTaskId: createdAlert.scheduledTaskId, - createdAt: response.body.createdAt, - updatedAt: response.body.updatedAt, - executionStatus: response.body.executionStatus, + scheduled_task_id: createdAlert.scheduled_task_id, + created_at: response.body.created_at, + updated_at: response.body.updated_at, + execution_status: response.body.execution_status, }); - expect(Date.parse(response.body.createdAt)).to.be.greaterThan(0); - expect(Date.parse(response.body.updatedAt)).to.be.greaterThan(0); - expect(Date.parse(response.body.updatedAt)).to.be.greaterThan( - Date.parse(response.body.createdAt) + expect(Date.parse(response.body.created_at)).to.be.greaterThan(0); + expect(Date.parse(response.body.updated_at)).to.be.greaterThan(0); + expect(Date.parse(response.body.updated_at)).to.be.greaterThan( + Date.parse(response.body.created_at) ); // Ensure AAD isn't broken await checkAAD({ @@ -153,16 +153,16 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { it('should handle update alert request appropriately when consumer is the same as producer', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ - alertTypeId: 'test.restricted-noop', + rule_type_id: 'test.restricted-noop', consumer: 'alertsRestrictedFixture', }) ) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); const updatedData = { name: 'bcd', @@ -173,10 +173,10 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { schedule: { interval: '12s' }, actions: [], throttle: '1m', - notifyWhen: 'onThrottleInterval', + notify_when: 'onThrottleInterval', }; const response = await supertestWithoutAuth - .put(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) + .put(`${getUrlPrefix(space.id)}/api/alerting/rule/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send(updatedData); @@ -204,23 +204,23 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { expect(response.body).to.eql({ ...updatedData, id: createdAlert.id, - alertTypeId: 'test.restricted-noop', + rule_type_id: 'test.restricted-noop', consumer: 'alertsRestrictedFixture', - createdBy: 'elastic', + created_by: 'elastic', enabled: true, - updatedBy: user.username, - apiKeyOwner: user.username, - muteAll: false, - mutedInstanceIds: [], - scheduledTaskId: createdAlert.scheduledTaskId, - createdAt: response.body.createdAt, - updatedAt: response.body.updatedAt, - executionStatus: response.body.executionStatus, + updated_by: user.username, + api_key_owner: user.username, + mute_all: false, + muted_alert_ids: [], + scheduled_task_id: createdAlert.scheduled_task_id, + created_at: response.body.created_at, + updated_at: response.body.updated_at, + execution_status: response.body.execution_status, }); - expect(Date.parse(response.body.createdAt)).to.be.greaterThan(0); - expect(Date.parse(response.body.updatedAt)).to.be.greaterThan(0); - expect(Date.parse(response.body.updatedAt)).to.be.greaterThan( - Date.parse(response.body.createdAt) + expect(Date.parse(response.body.created_at)).to.be.greaterThan(0); + expect(Date.parse(response.body.updated_at)).to.be.greaterThan(0); + expect(Date.parse(response.body.updated_at)).to.be.greaterThan( + Date.parse(response.body.created_at) ); // Ensure AAD isn't broken await checkAAD({ @@ -237,16 +237,16 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { it('should handle update alert request appropriately when consumer is not the producer', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ - alertTypeId: 'test.unrestricted-noop', + rule_type_id: 'test.unrestricted-noop', consumer: 'alertsFixture', }) ) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); const updatedData = { name: 'bcd', @@ -257,10 +257,10 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { schedule: { interval: '12s' }, actions: [], throttle: '1m', - notifyWhen: 'onThrottleInterval', + notify_when: 'onThrottleInterval', }; const response = await supertestWithoutAuth - .put(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) + .put(`${getUrlPrefix(space.id)}/api/alerting/rule/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send(updatedData); @@ -299,23 +299,23 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { expect(response.body).to.eql({ ...updatedData, id: createdAlert.id, - alertTypeId: 'test.unrestricted-noop', + rule_type_id: 'test.unrestricted-noop', consumer: 'alertsFixture', - createdBy: 'elastic', + created_by: 'elastic', enabled: true, - updatedBy: user.username, - apiKeyOwner: user.username, - muteAll: false, - mutedInstanceIds: [], - scheduledTaskId: createdAlert.scheduledTaskId, - createdAt: response.body.createdAt, - updatedAt: response.body.updatedAt, - executionStatus: response.body.executionStatus, + updated_by: user.username, + api_key_owner: user.username, + mute_all: false, + muted_alert_ids: [], + scheduled_task_id: createdAlert.scheduled_task_id, + created_at: response.body.created_at, + updated_at: response.body.updated_at, + execution_status: response.body.execution_status, }); - expect(Date.parse(response.body.createdAt)).to.be.greaterThan(0); - expect(Date.parse(response.body.updatedAt)).to.be.greaterThan(0); - expect(Date.parse(response.body.updatedAt)).to.be.greaterThan( - Date.parse(response.body.createdAt) + expect(Date.parse(response.body.created_at)).to.be.greaterThan(0); + expect(Date.parse(response.body.updated_at)).to.be.greaterThan(0); + expect(Date.parse(response.body.updated_at)).to.be.greaterThan( + Date.parse(response.body.created_at) ); // Ensure AAD isn't broken await checkAAD({ @@ -332,16 +332,16 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { it('should handle update alert request appropriately when consumer is "alerts"', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ - alertTypeId: 'test.restricted-noop', + rule_type_id: 'test.restricted-noop', consumer: 'alerts', }) ) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); const updatedData = { name: 'bcd', @@ -352,10 +352,10 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { schedule: { interval: '12s' }, actions: [], throttle: '1m', - notifyWhen: 'onThrottleInterval', + notify_when: 'onThrottleInterval', }; const response = await supertestWithoutAuth - .put(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) + .put(`${getUrlPrefix(space.id)}/api/alerting/rule/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send(updatedData); @@ -394,23 +394,23 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { expect(response.body).to.eql({ ...updatedData, id: createdAlert.id, - alertTypeId: 'test.restricted-noop', + rule_type_id: 'test.restricted-noop', consumer: 'alerts', - createdBy: 'elastic', + created_by: 'elastic', enabled: true, - updatedBy: user.username, - apiKeyOwner: user.username, - muteAll: false, - mutedInstanceIds: [], - scheduledTaskId: createdAlert.scheduledTaskId, - createdAt: response.body.createdAt, - updatedAt: response.body.updatedAt, - executionStatus: response.body.executionStatus, + updated_by: user.username, + api_key_owner: user.username, + mute_all: false, + muted_alert_ids: [], + scheduled_task_id: createdAlert.scheduled_task_id, + created_at: response.body.created_at, + updated_at: response.body.updated_at, + execution_status: response.body.execution_status, }); - expect(Date.parse(response.body.createdAt)).to.be.greaterThan(0); - expect(Date.parse(response.body.updatedAt)).to.be.greaterThan(0); - expect(Date.parse(response.body.updatedAt)).to.be.greaterThan( - Date.parse(response.body.createdAt) + expect(Date.parse(response.body.created_at)).to.be.greaterThan(0); + expect(Date.parse(response.body.updated_at)).to.be.greaterThan(0); + expect(Date.parse(response.body.updated_at)).to.be.greaterThan( + Date.parse(response.body.created_at) ); // Ensure AAD isn't broken await checkAAD({ @@ -427,11 +427,11 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { it('should still be able to update when AAD is broken', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); await retry.try(async () => { await supertest @@ -456,10 +456,10 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { schedule: { interval: '12s' }, actions: [], throttle: '1m', - notifyWhen: 'onThrottleInterval', + notify_when: 'onThrottleInterval', }; const response = await supertestWithoutAuth - .put(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) + .put(`${getUrlPrefix(space.id)}/api/alerting/rule/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send(updatedData); @@ -487,23 +487,23 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { expect(response.body).to.eql({ ...updatedData, id: createdAlert.id, - alertTypeId: 'test.noop', + rule_type_id: 'test.noop', consumer: 'alertsFixture', - createdBy: 'elastic', + created_by: 'elastic', enabled: true, - updatedBy: user.username, - apiKeyOwner: user.username, - muteAll: false, - mutedInstanceIds: [], - scheduledTaskId: createdAlert.scheduledTaskId, - createdAt: response.body.createdAt, - updatedAt: response.body.updatedAt, - executionStatus: response.body.executionStatus, + updated_by: user.username, + api_key_owner: user.username, + mute_all: false, + muted_alert_ids: [], + scheduled_task_id: createdAlert.scheduled_task_id, + created_at: response.body.created_at, + updated_at: response.body.updated_at, + execution_status: response.body.execution_status, }); - expect(Date.parse(response.body.createdAt)).to.be.greaterThan(0); - expect(Date.parse(response.body.updatedAt)).to.be.greaterThan(0); - expect(Date.parse(response.body.updatedAt)).to.be.greaterThan( - Date.parse(response.body.createdAt) + expect(Date.parse(response.body.created_at)).to.be.greaterThan(0); + expect(Date.parse(response.body.updated_at)).to.be.greaterThan(0); + expect(Date.parse(response.body.updated_at)).to.be.greaterThan( + Date.parse(response.body.created_at) ); // Ensure AAD isn't broken await checkAAD({ @@ -520,11 +520,11 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { it('should handle update alert request appropriately when alert name has leading and trailing whitespaces', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); const updatedData = { name: ' leading and trailing whitespace ', @@ -535,10 +535,10 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { schedule: { interval: '12s' }, actions: [], throttle: '1m', - notifyWhen: 'onThrottleInterval', + notify_when: 'onActiveAlert', }; const response = await supertestWithoutAuth - .put(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) + .put(`${getUrlPrefix(space.id)}/api/alerting/rule/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send(updatedData); @@ -572,14 +572,14 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { it(`shouldn't update alert from another space`, async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); const response = await supertestWithoutAuth - .put(`${getUrlPrefix('other')}/api/alerts/alert/${createdAlert.id}`) + .put(`${getUrlPrefix('other')}/api/alerting/rule/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send({ @@ -591,6 +591,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { schedule: { interval: '12s' }, throttle: '1m', actions: [], + notify_when: 'onActiveAlert', }); expect(response.statusCode).to.eql(404); @@ -615,26 +616,27 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { it('should handle update alert request appropriately when attempting to change alert type', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); const response = await supertestWithoutAuth - .put(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) + .put(`${getUrlPrefix(space.id)}/api/alerting/rule/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send({ name: 'bcd', tags: ['bar'], throttle: '1m', - alertTypeId: '1', + rule_type_id: '1', params: { foo: true, }, schedule: { interval: '12s' }, actions: [], + notify_when: 'onActiveAlert', }); switch (scenario.id) { @@ -649,7 +651,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { expect(response.body).to.eql({ statusCode: 400, error: 'Bad Request', - message: '[request body.alertTypeId]: definition for this key is missing', + message: '[request body.rule_type_id]: definition for this key is missing', }); break; default: @@ -659,7 +661,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { it('should handle update alert request appropriately when payload is empty and invalid', async () => { const response = await supertestWithoutAuth - .put(`${getUrlPrefix(space.id)}/api/alerts/alert/1`) + .put(`${getUrlPrefix(space.id)}/api/alerting/rule/1`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send({}); @@ -686,21 +688,21 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { it(`should handle update alert request appropriately when alertTypeConfig isn't valid`, async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ - alertTypeId: 'test.validation', + rule_type_id: 'test.validation', params: { param1: 'test', }, }) ) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); const response = await supertestWithoutAuth - .put(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) + .put(`${getUrlPrefix(space.id)}/api/alerting/rule/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send({ @@ -710,6 +712,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { throttle: '1m', params: {}, actions: [], + notify_when: 'onActiveAlert', }); switch (scenario.id) { @@ -746,7 +749,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { it('should handle update alert request appropriately when interval schedule is wrong syntax', async () => { const response = await supertestWithoutAuth - .put(`${getUrlPrefix(space.id)}/api/alerts/alert/1`) + .put(`${getUrlPrefix(space.id)}/api/alerting/rule/1`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send( @@ -779,7 +782,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { it('should handle updates to an alert schedule by rescheduling the underlying task', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ @@ -787,10 +790,10 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { }) ) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); await retry.try(async () => { - const alertTask = (await getAlertingTaskById(createdAlert.scheduledTaskId)).docs[0]; + const alertTask = (await getAlertingTaskById(createdAlert.scheduled_task_id)).docs[0]; expect(alertTask.status).to.eql('idle'); // ensure the alert inital run has completed and it's been rescheduled to half an hour from now ensureDatetimeIsWithinRange(Date.parse(alertTask.runAt), 30 * 60 * 1000); @@ -805,10 +808,10 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { schedule: { interval: '1m' }, actions: [], throttle: '1m', - notifyWhen: 'onThrottleInterval', + notify_when: 'onThrottleInterval', }; const response = await supertestWithoutAuth - .put(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) + .put(`${getUrlPrefix(space.id)}/api/alerting/rule/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send(updatedData); @@ -834,7 +837,8 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { case 'space_1_all_with_restricted_fixture at space1': expect(response.statusCode).to.eql(200); await retry.try(async () => { - const alertTask = (await getAlertingTaskById(createdAlert.scheduledTaskId)).docs[0]; + const alertTask = (await getAlertingTaskById(createdAlert.scheduled_task_id)) + .docs[0]; expect(alertTask.status).to.eql('idle'); // ensure the alert is rescheduled to a minute from now ensureDatetimeIsWithinRange(Date.parse(alertTask.runAt), 60 * 1000); @@ -847,21 +851,22 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { it('should handle updates for a long running alert type without failing the underlying tasks due to invalidated ApiKey', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send({ enabled: true, name: 'abc', tags: ['foo'], - alertTypeId: 'test.longRunning', + rule_type_id: 'test.longRunning', consumer: 'alertsFixture', schedule: { interval: '1s' }, throttle: '1m', actions: [], params: {}, + notify_when: 'onThrottleInterval', }) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); const updatedData = { name: 'bcd', tags: ['bar'], @@ -871,17 +876,17 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { schedule: { interval: '1m' }, actions: [], throttle: '1m', - notifyWhen: 'onThrottleInterval', + notify_when: 'onThrottleInterval', }; const response = await supertestWithoutAuth - .put(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) + .put(`${getUrlPrefix(space.id)}/api/alerting/rule/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send(updatedData); const statusUpdates: string[] = []; await retry.try(async () => { - const alertTask = (await getAlertingTaskById(createdAlert.scheduledTaskId)).docs[0]; + const alertTask = (await getAlertingTaskById(createdAlert.scheduled_task_id)).docs[0]; statusUpdates.push(alertTask.status); expect(alertTask.status).to.eql('idle'); }); @@ -909,7 +914,8 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { case 'space_1_all_with_restricted_fixture at space1': expect(response.statusCode).to.eql(200); await retry.try(async () => { - const alertTask = (await getAlertingTaskById(createdAlert.scheduledTaskId)).docs[0]; + const alertTask = (await getAlertingTaskById(createdAlert.scheduled_task_id)) + .docs[0]; expect(alertTask.status).to.eql('idle'); // ensure the alert is rescheduled to a minute from now ensureDatetimeIsWithinRange(Date.parse(alertTask.runAt), 60 * 1000); @@ -922,7 +928,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { it('should handle updates to an alert schedule by setting the new schedule for the underlying task', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ @@ -930,10 +936,10 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { }) ) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); await retry.try(async () => { - const alertTask = (await getAlertingTaskById(createdAlert.scheduledTaskId)).docs[0]; + const alertTask = (await getAlertingTaskById(createdAlert.scheduled_task_id)).docs[0]; expect(alertTask.status).to.eql('idle'); expect(alertTask.schedule).to.eql({ interval: '1m' }); }); @@ -947,10 +953,10 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { schedule: { interval: '1s' }, actions: [], throttle: '1m', - notifyWhen: 'onThrottleInterval', + notify_when: 'onThrottleInterval', }; const response = await supertestWithoutAuth - .put(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) + .put(`${getUrlPrefix(space.id)}/api/alerting/rule/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .send(updatedData); @@ -976,7 +982,8 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { case 'space_1_all_with_restricted_fixture at space1': expect(response.statusCode).to.eql(200); await retry.try(async () => { - const alertTask = (await getAlertingTaskById(createdAlert.scheduledTaskId)).docs[0]; + const alertTask = (await getAlertingTaskById(createdAlert.scheduled_task_id)) + .docs[0]; expect(alertTask.status).to.eql('idle'); expect(alertTask.schedule).to.eql({ interval: '1s' }); }); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update_api_key.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update_api_key.ts index d8e07812286a90..a434109a18933a 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update_api_key.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update_api_key.ts @@ -36,18 +36,18 @@ export default function createUpdateApiKeyTests({ getService }: FtrProviderConte describe(scenario.id, () => { it('should handle update alert api key request appropriately', async () => { const { body: createdAction } = await supertest - .post(`${getUrlPrefix(space.id)}/api/actions/action`) + .post(`${getUrlPrefix(space.id)}/api/actions/connector`) .set('kbn-xsrf', 'foo') .send({ name: 'MY action', - actionTypeId: 'test.noop', + connector_type_id: 'test.noop', config: {}, secrets: {}, }) .expect(200); const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ @@ -61,7 +61,7 @@ export default function createUpdateApiKeyTests({ getService }: FtrProviderConte }) ) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); const response = await alertUtils.getUpdateApiKeyRequest(createdAlert.id); switch (scenario.id) { @@ -93,11 +93,11 @@ export default function createUpdateApiKeyTests({ getService }: FtrProviderConte expect(response.statusCode).to.eql(204); expect(response.body).to.eql(''); const { body: updatedAlert } = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) + .get(`${getUrlPrefix(space.id)}/api/alerting/rule/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .expect(200); - expect(updatedAlert.apiKeyOwner).to.eql(user.username); + expect(updatedAlert.api_key_owner).to.eql(user.username); // Ensure AAD isn't broken await checkAAD({ supertest, @@ -113,16 +113,16 @@ export default function createUpdateApiKeyTests({ getService }: FtrProviderConte it('should handle update alert api key request appropriately when consumer is the same as producer', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ - alertTypeId: 'test.restricted-noop', + rule_type_id: 'test.restricted-noop', consumer: 'alertsRestrictedFixture', }) ) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); const response = await alertUtils.getUpdateApiKeyRequest(createdAlert.id); switch (scenario.id) { @@ -147,11 +147,11 @@ export default function createUpdateApiKeyTests({ getService }: FtrProviderConte expect(response.statusCode).to.eql(204); expect(response.body).to.eql(''); const { body: updatedAlert } = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) + .get(`${getUrlPrefix(space.id)}/api/alerting/rule/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .expect(200); - expect(updatedAlert.apiKeyOwner).to.eql(user.username); + expect(updatedAlert.api_key_owner).to.eql(user.username); // Ensure AAD isn't broken await checkAAD({ supertest, @@ -167,16 +167,16 @@ export default function createUpdateApiKeyTests({ getService }: FtrProviderConte it('should handle update alert api key request appropriately when consumer is not the producer', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ - alertTypeId: 'test.unrestricted-noop', + rule_type_id: 'test.unrestricted-noop', consumer: 'alertsFixture', }) ) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); const response = await alertUtils.getUpdateApiKeyRequest(createdAlert.id); switch (scenario.id) { @@ -212,11 +212,11 @@ export default function createUpdateApiKeyTests({ getService }: FtrProviderConte expect(response.statusCode).to.eql(204); expect(response.body).to.eql(''); const { body: updatedAlert } = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) + .get(`${getUrlPrefix(space.id)}/api/alerting/rule/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .expect(200); - expect(updatedAlert.apiKeyOwner).to.eql(user.username); + expect(updatedAlert.api_key_owner).to.eql(user.username); // Ensure AAD isn't broken await checkAAD({ supertest, @@ -232,16 +232,16 @@ export default function createUpdateApiKeyTests({ getService }: FtrProviderConte it('should handle update alert api key request appropriately when consumer is "alerts"', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ - alertTypeId: 'test.restricted-noop', + rule_type_id: 'test.restricted-noop', consumer: 'alerts', }) ) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); const response = await alertUtils.getUpdateApiKeyRequest(createdAlert.id); switch (scenario.id) { @@ -277,11 +277,11 @@ export default function createUpdateApiKeyTests({ getService }: FtrProviderConte expect(response.statusCode).to.eql(204); expect(response.body).to.eql(''); const { body: updatedAlert } = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) + .get(`${getUrlPrefix(space.id)}/api/alerting/rule/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .expect(200); - expect(updatedAlert.apiKeyOwner).to.eql(user.username); + expect(updatedAlert.api_key_owner).to.eql(user.username); // Ensure AAD isn't broken await checkAAD({ supertest, @@ -297,11 +297,11 @@ export default function createUpdateApiKeyTests({ getService }: FtrProviderConte it('should still be able to update API key when AAD is broken', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); await retry.try(async () => { await supertest @@ -341,11 +341,11 @@ export default function createUpdateApiKeyTests({ getService }: FtrProviderConte expect(response.statusCode).to.eql(204); expect(response.body).to.eql(''); const { body: updatedAlert } = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`) + .get(`${getUrlPrefix(space.id)}/api/alerting/rule/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .expect(200); - expect(updatedAlert.apiKeyOwner).to.eql(user.username); + expect(updatedAlert.api_key_owner).to.eql(user.username); // Ensure AAD isn't broken await checkAAD({ supertest, @@ -361,11 +361,11 @@ export default function createUpdateApiKeyTests({ getService }: FtrProviderConte it(`shouldn't update alert api key from another space`, async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix('other')}/api/alerts/alert`) + .post(`${getUrlPrefix('other')}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add('other', createdAlert.id, 'alert', 'alerts'); + objectRemover.add('other', createdAlert.id, 'rule', 'alerting'); const response = await alertUtils.getUpdateApiKeyRequest(createdAlert.id); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/aggregate.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/aggregate.ts index 140812bf2032a7..65aa38cb23c24f 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/aggregate.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/aggregate.ts @@ -21,12 +21,12 @@ export default function createAggregateTests({ getService }: FtrProviderContext) it('should aggregate when there are no alerts', async () => { const response = await supertest.get( - `${getUrlPrefix(Spaces.space1.id)}/api/alerts/_aggregate` + `${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rules/_aggregate` ); expect(response.status).to.eql(200); expect(response.body).to.eql({ - alertExecutionStatus: { + rule_execution_status: { ok: 0, active: 0, error: 0, @@ -45,12 +45,12 @@ export default function createAggregateTests({ getService }: FtrProviderContext) [...Array(NumOkAlerts)].map(async () => { const okAlertId = await createTestAlert( { - alertTypeId: 'test.noop', + rule_type_id: 'test.noop', schedule: { interval: '1s' }, }, 'ok' ); - objectRemover.add(Spaces.space1.id, okAlertId, 'alert', 'alerts'); + objectRemover.add(Spaces.space1.id, okAlertId, 'rule', 'alerting'); }) ); @@ -58,7 +58,7 @@ export default function createAggregateTests({ getService }: FtrProviderContext) [...Array(NumActiveAlerts)].map(async () => { const activeAlertId = await createTestAlert( { - alertTypeId: 'test.patternFiring', + rule_type_id: 'test.patternFiring', schedule: { interval: '1s' }, params: { pattern: { instance: new Array(100).fill(true) }, @@ -66,7 +66,7 @@ export default function createAggregateTests({ getService }: FtrProviderContext) }, 'active' ); - objectRemover.add(Spaces.space1.id, activeAlertId, 'alert', 'alerts'); + objectRemover.add(Spaces.space1.id, activeAlertId, 'rule', 'alerting'); }) ); @@ -74,12 +74,12 @@ export default function createAggregateTests({ getService }: FtrProviderContext) [...Array(NumErrorAlerts)].map(async () => { const activeAlertId = await createTestAlert( { - alertTypeId: 'test.throw', + rule_type_id: 'test.throw', schedule: { interval: '1s' }, }, 'error' ); - objectRemover.add(Spaces.space1.id, activeAlertId, 'alert', 'alerts'); + objectRemover.add(Spaces.space1.id, activeAlertId, 'rule', 'alerting'); }) ); @@ -88,12 +88,12 @@ export default function createAggregateTests({ getService }: FtrProviderContext) // too early. await delay(1000); const reponse = await supertest.get( - `${getUrlPrefix(Spaces.space1.id)}/api/alerts/_aggregate` + `${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rules/_aggregate` ); expect(reponse.status).to.eql(200); expect(reponse.body).to.eql({ - alertExecutionStatus: { + rule_execution_status: { ok: NumOkAlerts, active: NumActiveAlerts, error: NumErrorAlerts, @@ -102,6 +102,75 @@ export default function createAggregateTests({ getService }: FtrProviderContext) }, }); }); + + describe('legacy', () => { + it('should aggregate alert status totals', async () => { + const NumOkAlerts = 4; + const NumActiveAlerts = 1; + const NumErrorAlerts = 2; + + await Promise.all( + [...Array(NumOkAlerts)].map(async () => { + const okAlertId = await createTestAlert( + { + rule_type_id: 'test.noop', + schedule: { interval: '1s' }, + }, + 'ok' + ); + objectRemover.add(Spaces.space1.id, okAlertId, 'rule', 'alerting'); + }) + ); + + await Promise.all( + [...Array(NumActiveAlerts)].map(async () => { + const activeAlertId = await createTestAlert( + { + rule_type_id: 'test.patternFiring', + schedule: { interval: '1s' }, + params: { + pattern: { instance: new Array(100).fill(true) }, + }, + }, + 'active' + ); + objectRemover.add(Spaces.space1.id, activeAlertId, 'rule', 'alerting'); + }) + ); + + await Promise.all( + [...Array(NumErrorAlerts)].map(async () => { + const activeAlertId = await createTestAlert( + { + rule_type_id: 'test.throw', + schedule: { interval: '1s' }, + }, + 'error' + ); + objectRemover.add(Spaces.space1.id, activeAlertId, 'rule', 'alerting'); + }) + ); + + // Adding delay to allow ES refresh cycle to run. Even when the waitForStatus + // calls are successful, the call to aggregate may return stale totals if called + // too early. + await delay(1000); + const reponse = await supertest.get( + `${getUrlPrefix(Spaces.space1.id)}/api/alerts/_aggregate` + ); + + expect(reponse.status).to.eql(200); + expect(reponse.body).to.eql({ + alertExecutionStatus: { + ok: NumOkAlerts, + active: NumActiveAlerts, + error: NumErrorAlerts, + pending: 0, + unknown: 0, + }, + }); + }); + }); }); const WaitForStatusIncrement = 500; @@ -116,11 +185,11 @@ export default function createAggregateTests({ getService }: FtrProviderContext) } const response = await supertest.get( - `${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${id}` + `${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule/${id}` ); expect(response.status).to.eql(200); - const { executionStatus } = response.body || {}; + const { execution_status: executionStatus } = response.body || {}; const { status } = executionStatus || {}; const message = `waitForStatus(${Array.from(statuses)}): got ${JSON.stringify( @@ -144,7 +213,7 @@ export default function createAggregateTests({ getService }: FtrProviderContext) async function createTestAlert(testAlertOverrides = {}, status: string) { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send(getTestAlertData(testAlertOverrides)) .expect(200); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/alerts_base.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/alerts_base.ts index 49b4d7770962a8..89f6f7f54f4c37 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/alerts_base.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/alerts_base.ts @@ -46,11 +46,11 @@ export function alertTests({ getService }: FtrProviderContext, space: Space) { await esTestIndexTool.setup(); await es.indices.create({ index: authorizationIndex }); const { body: createdAction } = await supertestWithoutAuth - .post(`${getUrlPrefix(space.id)}/api/actions/action`) + .post(`${getUrlPrefix(space.id)}/api/actions/connector`) .set('kbn-xsrf', 'foo') .send({ name: 'My action', - actionTypeId: 'test.index-record', + connector_type_id: 'test.index-record', config: { unencrypted: `This value shouldn't get encrypted`, }, @@ -71,7 +71,7 @@ export function alertTests({ getService }: FtrProviderContext, space: Space) { after(async () => { await esTestIndexTool.destroy(); await es.indices.delete({ index: authorizationIndex }); - objectRemover.add(space.id, indexRecordActionId, 'action', 'actions'); + objectRemover.add(space.id, indexRecordActionId, 'connector', 'actions'); await objectRemover.removeAll(); }); @@ -142,11 +142,11 @@ instanceStateValue: true const reference = alertUtils.generateReference(); const { body: createdAction } = await supertestWithoutAuth - .post(`${getUrlPrefix(space.id)}/api/actions/action`) + .post(`${getUrlPrefix(space.id)}/api/actions/connector`) .set('kbn-xsrf', 'foo') .send({ name: 'MY action', - actionTypeId: 'test.noop', + connector_type_id: 'test.noop', config: {}, secrets: {}, }) @@ -158,11 +158,11 @@ instanceStateValue: true }; const createdAlert = await supertestWithoutAuth - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ - alertTypeId: 'test.patternFiring', + rule_type_id: 'test.patternFiring', schedule: { interval: '1s' }, throttle: null, params: { @@ -189,7 +189,7 @@ instanceStateValue: true expect(createdAlert.status).to.eql(200); const alertId = createdAlert.body.id; - objectRemover.add(space.id, alertId, 'alert', 'alerts'); + objectRemover.add(space.id, alertId, 'rule', 'alerting'); const actionTestRecord = ( await esTestIndexTool.waitForDocs('action:test.index-record', reference) @@ -203,11 +203,11 @@ instanceStateValue: true const reference = alertUtils.generateReference(); const { body: createdAction } = await supertestWithoutAuth - .post(`${getUrlPrefix(space.id)}/api/actions/action`) + .post(`${getUrlPrefix(space.id)}/api/actions/connector`) .set('kbn-xsrf', 'foo') .send({ name: 'MY action', - actionTypeId: 'test.noop', + connector_type_id: 'test.noop', config: {}, secrets: {}, }) @@ -219,11 +219,11 @@ instanceStateValue: true }; // created disabled alert const createdAlert = await supertestWithoutAuth - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ - alertTypeId: 'test.patternFiring', + rule_type_id: 'test.patternFiring', schedule: { interval: '1s' }, enabled: false, throttle: null, @@ -262,7 +262,7 @@ instanceStateValue: true const actionTestRecord = await esTestIndexTool.search('action:test.index-record', reference); expect(actionTestRecord.hits.total.value).to.eql(0); - objectRemover.add(space.id, alertId, 'alert', 'alerts'); + objectRemover.add(space.id, alertId, 'rule', 'alerting'); }); it('should reschedule failing alerts using the Task Manager retry logic with alert schedule interval', async () => { @@ -282,7 +282,7 @@ instanceStateValue: true await esTestIndexTool.waitForDocs('alert:test.failing', reference); await retry.try(async () => { - const alertTask = (await getAlertingTaskById(response.body.scheduledTaskId)).docs[0]; + const alertTask = (await getAlertingTaskById(response.body.scheduled_task_id)).docs[0]; expect(alertTask.status).to.eql('idle'); expect(alertTask.schedule.interval).to.eql('10s'); // ensure the alert is rescheduled correctly @@ -301,24 +301,24 @@ instanceStateValue: true const retryDate = new Date(Date.now() + 60000); const { body: createdAction } = await supertestWithoutAuth - .post(`${getUrlPrefix(space.id)}/api/actions/action`) + .post(`${getUrlPrefix(space.id)}/api/actions/connector`) .set('kbn-xsrf', 'foo') .send({ name: 'Test rate limit', - actionTypeId: 'test.rate-limit', + connector_type_id: 'test.rate-limit', config: {}, }) .expect(200); - objectRemover.add(space.id, createdAction.id, 'action', 'actions'); + objectRemover.add(space.id, createdAction.id, 'connector', 'actions'); const reference = alertUtils.generateReference(); const response = await supertestWithoutAuth - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ schedule: { interval: '1m' }, - alertTypeId: 'test.always-firing', + rule_type_id: 'test.always-firing', params: { index: ES_TEST_INDEX_NAME, reference: 'create-test-2', @@ -338,7 +338,7 @@ instanceStateValue: true ); expect(response.statusCode).to.eql(200); - objectRemover.add(space.id, response.body.id, 'alert', 'alerts'); + objectRemover.add(space.id, response.body.id, 'rule', 'alerting'); const scheduledActionTask = await retry.try(async () => { const searchResult = await es.search({ index: '.kibana_task_manager', @@ -382,11 +382,11 @@ instanceStateValue: true it('should have proper callCluster and savedObjectsClient authorization for alert type executor', async () => { const reference = alertUtils.generateReference(); const response = await supertestWithoutAuth - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ - alertTypeId: 'test.authorization', + rule_type_id: 'test.authorization', params: { callClusterAuthorizationIndex: authorizationIndex, savedObjectsClientType: 'dashboard', @@ -398,7 +398,7 @@ instanceStateValue: true ); expect(response.statusCode).to.eql(200); - objectRemover.add(space.id, response.body.id, 'alert', 'alerts'); + objectRemover.add(space.id, response.body.id, 'rule', 'alerting'); const alertTestRecord = ( await esTestIndexTool.waitForDocs('alert:test.authorization', reference) )[0]; @@ -419,20 +419,20 @@ instanceStateValue: true it('should have proper callCluster and savedObjectsClient authorization for action type executor', async () => { const reference = alertUtils.generateReference(); const { body: createdAction } = await supertestWithoutAuth - .post(`${getUrlPrefix(space.id)}/api/actions/action`) + .post(`${getUrlPrefix(space.id)}/api/actions/connector`) .set('kbn-xsrf', 'foo') .send({ name: 'My action', - actionTypeId: 'test.authorization', + connector_type_id: 'test.authorization', }) .expect(200); - objectRemover.add(space.id, createdAction.id, 'action', 'actions'); + objectRemover.add(space.id, createdAction.id, 'connector', 'actions'); const response = await supertestWithoutAuth - .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ - alertTypeId: 'test.always-firing', + rule_type_id: 'test.always-firing', params: { index: ES_TEST_INDEX_NAME, reference, @@ -454,7 +454,7 @@ instanceStateValue: true ); expect(response.statusCode).to.eql(200); - objectRemover.add(space.id, response.body.id, 'alert', 'alerts'); + objectRemover.add(space.id, response.body.id, 'rule', 'alerting'); const actionTestRecord = ( await esTestIndexTool.waitForDocs('action:test.authorization', reference) )[0]; diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/es_query/alert.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/es_query/alert.ts index 777caacd465d8f..8511bcdf89d3bf 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/es_query/alert.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/es_query/alert.ts @@ -207,15 +207,16 @@ export default function alertTests({ getService }: FtrProviderContext) { }; const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send({ name: params.name, consumer: 'alerts', enabled: true, - alertTypeId: ALERT_TYPE_ID, + rule_type_id: ALERT_TYPE_ID, schedule: { interval: `${ALERT_INTERVAL_SECONDS}s` }, actions: [action], + notify_when: 'onActiveAlert', params: { index: [ES_TEST_INDEX_NAME], timeField: params.timeField || 'date', @@ -230,7 +231,7 @@ export default function alertTests({ getService }: FtrProviderContext) { .expect(200); const alertId = createdAlert.id; - objectRemover.add(Spaces.space1.id, alertId, 'alert', 'alerts'); + objectRemover.add(Spaces.space1.id, alertId, 'rule', 'alerting'); return alertId; } @@ -239,11 +240,11 @@ export default function alertTests({ getService }: FtrProviderContext) { async function createAction(supertest: any, objectRemover: ObjectRemover): Promise { const { body: createdAction } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/action`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connector`) .set('kbn-xsrf', 'foo') .send({ name: 'index action for es query FT', - actionTypeId: ACTION_TYPE_ID, + connector_type_id: ACTION_TYPE_ID, config: { index: ES_TEST_OUTPUT_INDEX_NAME, }, @@ -252,7 +253,7 @@ async function createAction(supertest: any, objectRemover: ObjectRemover): Promi .expect(200); const actionId = createdAction.id; - objectRemover.add(Spaces.space1.id, actionId, 'action', 'actions'); + objectRemover.add(Spaces.space1.id, actionId, 'connector', 'actions'); return actionId; } diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/alert.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/alert.ts index e0275f3ead37aa..3d7e391d7530fa 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/alert.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/alert.ts @@ -357,15 +357,16 @@ export default function alertTests({ getService }: FtrProviderContext) { }; const { status, body: createdAlert } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send({ name: params.name, consumer: 'alerts', enabled: true, - alertTypeId: ALERT_TYPE_ID, + rule_type_id: ALERT_TYPE_ID, schedule: { interval: `${ALERT_INTERVAL_SECONDS}s` }, actions: [action], + notify_when: 'onActiveAlert', params: { index: ES_TEST_INDEX_NAME, timeField: params.timeField || 'date', @@ -387,7 +388,7 @@ export default function alertTests({ getService }: FtrProviderContext) { expect(status).to.be(200); const alertId = createdAlert.id; - objectRemover.add(Spaces.space1.id, alertId, 'alert', 'alerts'); + objectRemover.add(Spaces.space1.id, alertId, 'rule', 'alerting'); return alertId; } @@ -396,11 +397,11 @@ export default function alertTests({ getService }: FtrProviderContext) { async function createAction(supertest: any, objectRemover: ObjectRemover): Promise { const { statusCode, body: createdAction } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/action`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connector`) .set('kbn-xsrf', 'foo') .send({ name: 'index action for index threshold FT', - actionTypeId: ACTION_TYPE_ID, + connector_type_id: ACTION_TYPE_ID, config: { index: ES_TEST_OUTPUT_INDEX_NAME, }, @@ -413,7 +414,7 @@ async function createAction(supertest: any, objectRemover: ObjectRemover): Promi expect(statusCode).to.be(200); const actionId = createdAction.id; - objectRemover.add(Spaces.space1.id, actionId, 'action', 'actions'); + objectRemover.add(Spaces.space1.id, actionId, 'connector', 'actions'); return actionId; } diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts index 6e0bf867c95877..9033b1f303943b 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts @@ -35,18 +35,18 @@ export default function createAlertTests({ getService }: FtrProviderContext) { it('should handle create alert request appropriately', async () => { const { body: createdAction } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/action`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connector`) .set('kbn-xsrf', 'foo') .send({ name: 'MY action', - actionTypeId: 'test.noop', + connector_type_id: 'test.noop', config: {}, secrets: {}, }) .expect(200); const response = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ @@ -61,7 +61,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { ); expect(response.status).to.eql(200); - objectRemover.add(Spaces.space1.id, response.body.id, 'alert', 'alerts'); + objectRemover.add(Spaces.space1.id, response.body.id, 'rule', 'alerting'); expect(response.body).to.eql({ id: response.body.id, name: 'abc', @@ -69,34 +69,34 @@ export default function createAlertTests({ getService }: FtrProviderContext) { actions: [ { id: createdAction.id, - actionTypeId: createdAction.actionTypeId, + connector_type_id: createdAction.connector_type_id, group: 'default', params: {}, }, ], enabled: true, - alertTypeId: 'test.noop', + rule_type_id: 'test.noop', consumer: 'alertsFixture', params: {}, - createdBy: null, + created_by: null, schedule: { interval: '1m' }, - scheduledTaskId: response.body.scheduledTaskId, - updatedBy: null, - apiKeyOwner: null, + scheduled_task_id: response.body.scheduled_task_id, + updated_by: null, + api_key_owner: null, throttle: '1m', - notifyWhen: 'onThrottleInterval', - muteAll: false, - mutedInstanceIds: [], - createdAt: response.body.createdAt, - updatedAt: response.body.updatedAt, - executionStatus: response.body.executionStatus, + notify_when: 'onThrottleInterval', + mute_all: false, + muted_alert_ids: [], + created_at: response.body.created_at, + updated_at: response.body.updated_at, + execution_status: response.body.execution_status, }); - expect(Date.parse(response.body.createdAt)).to.be.greaterThan(0); - expect(Date.parse(response.body.updatedAt)).to.be.greaterThan(0); - expect(Date.parse(response.body.updatedAt)).to.eql(Date.parse(response.body.createdAt)); + expect(Date.parse(response.body.created_at)).to.be.greaterThan(0); + expect(Date.parse(response.body.updated_at)).to.be.greaterThan(0); + expect(Date.parse(response.body.updated_at)).to.eql(Date.parse(response.body.created_at)); - expect(typeof response.body.scheduledTaskId).to.be('string'); - const { _source: taskRecord } = await getScheduledTask(response.body.scheduledTaskId); + expect(typeof response.body.scheduled_task_id).to.be('string'); + const { _source: taskRecord } = await getScheduledTask(response.body.scheduled_task_id); expect(taskRecord.type).to.eql('task'); expect(taskRecord.task.taskType).to.eql('alerting:test.noop'); expect(JSON.parse(taskRecord.task.params)).to.eql({ @@ -115,12 +115,12 @@ export default function createAlertTests({ getService }: FtrProviderContext) { it('should allow providing custom saved object ids (uuid v1)', async () => { const customId = '09570bb0-6299-11eb-8fde-9fe5ce6ea450'; const response = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${customId}`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule/${customId}`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()); expect(response.status).to.eql(200); - objectRemover.add(Spaces.space1.id, response.body.id, 'alert', 'alerts'); + objectRemover.add(Spaces.space1.id, response.body.id, 'rule', 'alerting'); expect(response.body.id).to.eql(customId); // Ensure AAD isn't broken await checkAAD({ @@ -134,12 +134,12 @@ export default function createAlertTests({ getService }: FtrProviderContext) { it('should allow providing custom saved object ids (uuid v4)', async () => { const customId = 'b3bc6d83-3192-4ffd-9702-ad4fb88617ba'; const response = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${customId}`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule/${customId}`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()); expect(response.status).to.eql(200); - objectRemover.add(Spaces.space1.id, response.body.id, 'alert', 'alerts'); + objectRemover.add(Spaces.space1.id, response.body.id, 'rule', 'alerting'); expect(response.body.id).to.eql(customId); // Ensure AAD isn't broken await checkAAD({ @@ -153,7 +153,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { it('should not allow providing simple custom ids (non uuid)', async () => { const customId = '1'; const response = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${customId}`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule/${customId}`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()); @@ -169,13 +169,13 @@ export default function createAlertTests({ getService }: FtrProviderContext) { it('should return 409 when document with id already exists', async () => { const customId = '5031f8f0-629a-11eb-b500-d1931a8e5df7'; const createdAlertResponse = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${customId}`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule/${customId}`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(Spaces.space1.id, createdAlertResponse.body.id, 'alert', 'alerts'); + objectRemover.add(Spaces.space1.id, createdAlertResponse.body.id, 'rule', 'alerting'); await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${customId}`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule/${customId}`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(409); @@ -183,7 +183,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { it('should handle create alert request appropriately when consumer is unknown', async () => { const response = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send(getTestAlertData({ consumer: 'some consumer patrick invented' })); @@ -201,13 +201,101 @@ export default function createAlertTests({ getService }: FtrProviderContext) { it('should handle create alert request appropriately when an alert is disabled ', async () => { const response = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send(getTestAlertData({ enabled: false })); expect(response.status).to.eql(200); - objectRemover.add(Spaces.space1.id, response.body.id, 'alert', 'alerts'); + objectRemover.add(Spaces.space1.id, response.body.id, 'rule', 'alerting'); expect(response.body.scheduledTaskId).to.eql(undefined); }); + + describe('legacy', () => { + it('should handle create alert request appropriately', async () => { + const { body: createdAction } = await supertest + .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connector`) + .set('kbn-xsrf', 'foo') + .send({ + name: 'MY action', + connector_type_id: 'test.noop', + config: {}, + secrets: {}, + }) + .expect(200); + + const { + rule_type_id: alertTypeId, + notify_when: notifyWhen, + ...testAlert + } = getTestAlertData({ + actions: [ + { + id: createdAction.id, + group: 'default', + params: {}, + }, + ], + }); + const response = await supertest + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) + .set('kbn-xsrf', 'foo') + .send({ + ...testAlert, + alertTypeId, + notifyWhen, + }); + + expect(response.status).to.eql(200); + objectRemover.add(Spaces.space1.id, response.body.id, 'rule', 'alerting'); + expect(response.body).to.eql({ + id: response.body.id, + name: 'abc', + tags: ['foo'], + actions: [ + { + id: createdAction.id, + actionTypeId: createdAction.connector_type_id, + group: 'default', + params: {}, + }, + ], + enabled: true, + alertTypeId: 'test.noop', + consumer: 'alertsFixture', + params: {}, + createdBy: null, + schedule: { interval: '1m' }, + scheduledTaskId: response.body.scheduledTaskId, + updatedBy: null, + apiKeyOwner: null, + throttle: '1m', + notifyWhen: 'onThrottleInterval', + muteAll: false, + mutedInstanceIds: [], + createdAt: response.body.createdAt, + updatedAt: response.body.updatedAt, + executionStatus: response.body.executionStatus, + }); + expect(Date.parse(response.body.createdAt)).to.be.greaterThan(0); + expect(Date.parse(response.body.updatedAt)).to.be.greaterThan(0); + expect(Date.parse(response.body.updatedAt)).to.eql(Date.parse(response.body.createdAt)); + + expect(typeof response.body.scheduledTaskId).to.be('string'); + const { _source: taskRecord } = await getScheduledTask(response.body.scheduledTaskId); + expect(taskRecord.type).to.eql('task'); + expect(taskRecord.task.taskType).to.eql('alerting:test.noop'); + expect(JSON.parse(taskRecord.task.params)).to.eql({ + alertId: response.body.id, + spaceId: Spaces.space1.id, + }); + // Ensure AAD isn't broken + await checkAAD({ + supertest, + spaceId: Spaces.space1.id, + type: 'alert', + id: response.body.id, + }); + }); + }); }); } diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/delete.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/delete.ts index 4e033eabc2ac2f..04ae217d477606 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/delete.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/delete.ts @@ -29,13 +29,13 @@ export default function createDeleteTests({ getService }: FtrProviderContext) { it('should handle delete alert request appropriately', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); await supertest - .delete(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}`) + .delete(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .expect(204, ''); @@ -49,13 +49,13 @@ export default function createDeleteTests({ getService }: FtrProviderContext) { it(`shouldn't delete alert from another space`, async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); await supertest - .delete(`${getUrlPrefix(Spaces.other.id)}/api/alerts/alert/${createdAlert.id}`) + .delete(`${getUrlPrefix(Spaces.other.id)}/api/alerting/rule/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .expect(404, { statusCode: 404, @@ -63,5 +63,27 @@ export default function createDeleteTests({ getService }: FtrProviderContext) { message: `Saved object [alert/${createdAlert.id}] not found`, }); }); + + describe('legacy', () => { + it('should handle delete alert request appropriately', async () => { + const { body: createdAlert } = await supertest + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send(getTestAlertData()) + .expect(200); + + await supertest + .delete(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}`) + .set('kbn-xsrf', 'foo') + .expect(204, ''); + + try { + await getScheduledTask(createdAlert.scheduledTaskId); + throw new Error('Should have removed scheduled task'); + } catch (e) { + expect(e.status).to.eql(404); + } + }); + }); }); } diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/disable.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/disable.ts index b2bb24aec902d6..60749343cf269d 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/disable.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/disable.ts @@ -36,11 +36,11 @@ export default function createDisableAlertTests({ getService }: FtrProviderConte it('should handle disable alert request appropriately', async () => { const { body: createdAlert } = await supertestWithoutAuth - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send(getTestAlertData({ enabled: true })) .expect(200); - objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'rule', 'alerting'); await alertUtils.disable(createdAlert.id); @@ -62,11 +62,11 @@ export default function createDisableAlertTests({ getService }: FtrProviderConte it(`shouldn't disable alert from another space`, async () => { const { body: createdAlert } = await supertestWithoutAuth - .post(`${getUrlPrefix(Spaces.other.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(Spaces.other.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send(getTestAlertData({ enabled: true })) .expect(200); - objectRemover.add(Spaces.other.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(Spaces.other.id, createdAlert.id, 'rule', 'alerting'); await alertUtils.getDisableRequest(createdAlert.id).expect(404, { statusCode: 404, @@ -74,5 +74,36 @@ export default function createDisableAlertTests({ getService }: FtrProviderConte message: `Saved object [alert/${createdAlert.id}] not found`, }); }); + + describe('legacy', () => { + it('should handle disable alert request appropriately', async () => { + const { body: createdAlert } = await supertestWithoutAuth + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send(getTestAlertData({ enabled: true })) + .expect(200); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'rule', 'alerting'); + + await supertestWithoutAuth + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}/_disable`) + .set('kbn-xsrf', 'foo') + .expect(204); + + try { + await getScheduledTask(createdAlert.scheduledTaskId); + throw new Error('Should have removed scheduled task'); + } catch (e) { + expect(e.status).to.eql(404); + } + + // Ensure AAD isn't broken + await checkAAD({ + supertest: supertestWithoutAuth, + spaceId: Spaces.space1.id, + type: 'alert', + id: createdAlert.id, + }); + }); + }); }); } diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/enable.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/enable.ts index 29347a2d1d3980..f0b0d1f34a2770 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/enable.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/enable.ts @@ -36,20 +36,20 @@ export default function createEnableAlertTests({ getService }: FtrProviderContex it('should handle enable alert request appropriately', async () => { const { body: createdAlert } = await supertestWithoutAuth - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send(getTestAlertData({ enabled: false })) .expect(200); - objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'rule', 'alerting'); await alertUtils.enable(createdAlert.id); const { body: updatedAlert } = await supertestWithoutAuth - .get(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}`) + .get(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .expect(200); - expect(typeof updatedAlert.scheduledTaskId).to.eql('string'); - const { _source: taskRecord } = await getScheduledTask(updatedAlert.scheduledTaskId); + expect(typeof updatedAlert.scheduled_task_id).to.eql('string'); + const { _source: taskRecord } = await getScheduledTask(updatedAlert.scheduled_task_id); expect(taskRecord.type).to.eql('task'); expect(taskRecord.task.taskType).to.eql('alerting:test.noop'); expect(JSON.parse(taskRecord.task.params)).to.eql({ @@ -68,11 +68,11 @@ export default function createEnableAlertTests({ getService }: FtrProviderContex it(`shouldn't enable alert from another space`, async () => { const { body: createdAlert } = await supertestWithoutAuth - .post(`${getUrlPrefix(Spaces.other.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(Spaces.other.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send(getTestAlertData({ enabled: false })) .expect(200); - objectRemover.add(Spaces.other.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(Spaces.other.id, createdAlert.id, 'rule', 'alerting'); await alertUtils.getEnableRequest(createdAlert.id).expect(404, { statusCode: 404, @@ -80,5 +80,42 @@ export default function createEnableAlertTests({ getService }: FtrProviderContex message: `Saved object [alert/${createdAlert.id}] not found`, }); }); + + describe('legacy', () => { + it('should handle enable alert request appropriately', async () => { + const { body: createdAlert } = await supertestWithoutAuth + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send(getTestAlertData({ enabled: false })) + .expect(200); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'rule', 'alerting'); + + await supertestWithoutAuth + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}/_enable`) + .set('kbn-xsrf', 'foo') + .expect(204); + + const { body: updatedAlert } = await supertestWithoutAuth + .get(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule/${createdAlert.id}`) + .set('kbn-xsrf', 'foo') + .expect(200); + expect(typeof updatedAlert.scheduled_task_id).to.eql('string'); + const { _source: taskRecord } = await getScheduledTask(updatedAlert.scheduled_task_id); + expect(taskRecord.type).to.eql('task'); + expect(taskRecord.task.taskType).to.eql('alerting:test.noop'); + expect(JSON.parse(taskRecord.task.params)).to.eql({ + alertId: createdAlert.id, + spaceId: Spaces.space1.id, + }); + + // Ensure AAD isn't broken + await checkAAD({ + supertest: supertestWithoutAuth, + spaceId: Spaces.space1.id, + type: 'alert', + id: createdAlert.id, + }); + }); + }); }); } diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/event_log.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/event_log.ts index 2e97711cdef2c0..5d54fe3d2b1f78 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/event_log.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/event_log.ts @@ -26,11 +26,11 @@ export default function eventLogTests({ getService }: FtrProviderContext) { it('should generate expected events for normal operation', async () => { const { body: createdAction } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/action`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connector`) .set('kbn-xsrf', 'foo') .send({ name: 'MY action', - actionTypeId: 'test.noop', + connector_type_id: 'test.noop', config: {}, secrets: {}, }) @@ -42,11 +42,11 @@ export default function eventLogTests({ getService }: FtrProviderContext) { }; const response = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ - alertTypeId: 'test.patternFiring', + rule_type_id: 'test.patternFiring', schedule: { interval: '1s' }, throttle: null, params: { @@ -64,7 +64,7 @@ export default function eventLogTests({ getService }: FtrProviderContext) { expect(response.status).to.eql(200); const alertId = response.body.id; - objectRemover.add(Spaces.space1.id, alertId, 'alert', 'alerts'); + objectRemover.add(Spaces.space1.id, alertId, 'rule', 'alerting'); // get the events we're expecting const events = await retry.try(async () => { @@ -174,11 +174,11 @@ export default function eventLogTests({ getService }: FtrProviderContext) { it('should generate expected events for normal operation with subgroups', async () => { const { body: createdAction } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/action`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connector`) .set('kbn-xsrf', 'foo') .send({ name: 'MY action', - actionTypeId: 'test.noop', + connector_type_id: 'test.noop', config: {}, secrets: {}, }) @@ -191,11 +191,11 @@ export default function eventLogTests({ getService }: FtrProviderContext) { }; const response = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ - alertTypeId: 'test.patternFiring', + rule_type_id: 'test.patternFiring', schedule: { interval: '1s' }, throttle: null, params: { @@ -213,7 +213,7 @@ export default function eventLogTests({ getService }: FtrProviderContext) { expect(response.status).to.eql(200); const alertId = response.body.id; - objectRemover.add(Spaces.space1.id, alertId, 'alert', 'alerts'); + objectRemover.add(Spaces.space1.id, alertId, 'rule', 'alerting'); // get the events we're expecting const events = await retry.try(async () => { @@ -315,11 +315,11 @@ export default function eventLogTests({ getService }: FtrProviderContext) { it('should generate events for execution errors', async () => { const response = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ - alertTypeId: 'test.throw', + rule_type_id: 'test.throw', schedule: { interval: '1s' }, throttle: null, }) @@ -327,7 +327,7 @@ export default function eventLogTests({ getService }: FtrProviderContext) { expect(response.status).to.eql(200); const alertId = response.body.id; - objectRemover.add(Spaces.space1.id, alertId, 'alert', 'alerts'); + objectRemover.add(Spaces.space1.id, alertId, 'rule', 'alerting'); const events = await retry.try(async () => { return await getEventLog({ diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/execution_status.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/execution_status.ts index f2a79b72dc7317..0f7ed80cfd38df 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/execution_status.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/execution_status.ts @@ -28,15 +28,19 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon it('should be "pending" for newly created alert', async () => { const dateStart = Date.now(); const response = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()); const dateEnd = Date.now(); expect(response.status).to.eql(200); - objectRemover.add(Spaces.space1.id, response.body.id, 'alert', 'alerts'); - - expect(response.body.executionStatus).to.be.ok(); - const { status, lastExecutionDate, error } = response.body.executionStatus; + objectRemover.add(Spaces.space1.id, response.body.id, 'rule', 'alerting'); + + expect(response.body.execution_status).to.be.ok(); + const { + error, + status, + last_execution_date: lastExecutionDate, + } = response.body.execution_status; expect(status).to.be('pending'); ensureDatetimesAreOrdered([dateStart, lastExecutionDate, dateEnd]); expect(error).not.to.be.ok(); @@ -54,25 +58,25 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon const dates = []; dates.push(Date.now()); const response = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ - alertTypeId: 'test.noop', + rule_type_id: 'test.noop', schedule: { interval: '1s' }, }) ); expect(response.status).to.eql(200); const alertId = response.body.id; - const alertUpdatedAt = response.body.updatedAt; - dates.push(response.body.executionStatus.lastExecutionDate); - objectRemover.add(Spaces.space1.id, alertId, 'alert', 'alerts'); + const alertUpdatedAt = response.body.updated_at; + dates.push(response.body.execution_status.last_execution_date); + objectRemover.add(Spaces.space1.id, alertId, 'rule', 'alerting'); const executionStatus = await waitForStatus(alertId, new Set(['ok'])); - dates.push(executionStatus.lastExecutionDate); + dates.push(executionStatus.last_execution_date); dates.push(Date.now()); ensureDatetimesAreOrdered(dates); - ensureAlertUpdatedAtHasNotChanged(alertId, alertUpdatedAt); + await ensureAlertUpdatedAtHasNotChanged(alertId, alertUpdatedAt); // Ensure AAD isn't broken await checkAAD({ @@ -87,11 +91,11 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon const dates = []; dates.push(Date.now()); const response = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ - alertTypeId: 'test.patternFiring', + rule_type_id: 'test.patternFiring', schedule: { interval: '1s' }, params: { pattern: { instance: trues(100) }, @@ -100,15 +104,15 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon ); expect(response.status).to.eql(200); const alertId = response.body.id; - const alertUpdatedAt = response.body.updatedAt; - dates.push(response.body.executionStatus.lastExecutionDate); - objectRemover.add(Spaces.space1.id, alertId, 'alert', 'alerts'); + const alertUpdatedAt = response.body.updated_at; + dates.push(response.body.execution_status.last_execution_date); + objectRemover.add(Spaces.space1.id, alertId, 'rule', 'alerting'); const executionStatus = await waitForStatus(alertId, new Set(['active'])); - dates.push(executionStatus.lastExecutionDate); + dates.push(executionStatus.last_execution_date); dates.push(Date.now()); ensureDatetimesAreOrdered(dates); - ensureAlertUpdatedAtHasNotChanged(alertId, alertUpdatedAt); + await ensureAlertUpdatedAtHasNotChanged(alertId, alertUpdatedAt); // Ensure AAD isn't broken await checkAAD({ @@ -123,25 +127,25 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon const dates = []; dates.push(Date.now()); const response = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ - alertTypeId: 'test.throw', + rule_type_id: 'test.throw', schedule: { interval: '1s' }, }) ); expect(response.status).to.eql(200); const alertId = response.body.id; - const alertUpdatedAt = response.body.updatedAt; - dates.push(response.body.executionStatus.lastExecutionDate); - objectRemover.add(Spaces.space1.id, alertId, 'alert', 'alerts'); + const alertUpdatedAt = response.body.updated_at; + dates.push(response.body.execution_status.last_execution_date); + objectRemover.add(Spaces.space1.id, alertId, 'rule', 'alerting'); const executionStatus = await waitForStatus(alertId, new Set(['error'])); - dates.push(executionStatus.lastExecutionDate); + dates.push(executionStatus.last_execution_date); dates.push(Date.now()); ensureDatetimesAreOrdered(dates); - ensureAlertUpdatedAtHasNotChanged(alertId, alertUpdatedAt); + await ensureAlertUpdatedAtHasNotChanged(alertId, alertUpdatedAt); // Ensure AAD isn't broken await checkAAD({ @@ -159,41 +163,41 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon it('should eventually have error reason "execute" when appropriate', async () => { const response = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ - alertTypeId: 'test.throw', + rule_type_id: 'test.throw', schedule: { interval: '1s' }, }) ); expect(response.status).to.eql(200); const alertId = response.body.id; - const alertUpdatedAt = response.body.updatedAt; - objectRemover.add(Spaces.space1.id, alertId, 'alert', 'alerts'); + const alertUpdatedAt = response.body.updated_at; + objectRemover.add(Spaces.space1.id, alertId, 'rule', 'alerting'); const executionStatus = await waitForStatus(alertId, new Set(['error'])); expect(executionStatus.error).to.be.ok(); expect(executionStatus.error.reason).to.be('execute'); expect(executionStatus.error.message).to.be('this alert is intended to fail'); - ensureAlertUpdatedAtHasNotChanged(alertId, alertUpdatedAt); + await ensureAlertUpdatedAtHasNotChanged(alertId, alertUpdatedAt); }); it('should eventually have error reason "unknown" when appropriate', async () => { const response = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ - alertTypeId: 'test.validation', + rule_type_id: 'test.validation', schedule: { interval: '1s' }, params: { param1: 'valid now, but will change to a number soon!' }, }) ); expect(response.status).to.eql(200); const alertId = response.body.id; - const alertUpdatedAt = response.body.updatedAt; - objectRemover.add(Spaces.space1.id, alertId, 'alert', 'alerts'); + const alertUpdatedAt = response.body.updated_at; + objectRemover.add(Spaces.space1.id, alertId, 'rule', 'alerting'); let executionStatus = await waitForStatus(alertId, new Set(['ok'])); @@ -211,7 +215,7 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon executionStatus = await waitForStatus(alertId, new Set(['error'])); expect(executionStatus.error).to.be.ok(); expect(executionStatus.error.reason).to.be('unknown'); - ensureAlertUpdatedAtHasNotChanged(alertId, alertUpdatedAt); + await ensureAlertUpdatedAtHasNotChanged(alertId, alertUpdatedAt); const message = 'params invalid: [param1]: expected value of type [string] but got [number]'; expect(executionStatus.error.message).to.be(message); @@ -220,17 +224,17 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon it('should be able to find over all the fields', async () => { const startDate = Date.now(); const createResponse = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ - alertTypeId: 'test.throw', + rule_type_id: 'test.throw', schedule: { interval: '1s' }, }) ); expect(createResponse.status).to.eql(200); const alertId = createResponse.body.id; - objectRemover.add(Spaces.space1.id, alertId, 'alert', 'alerts'); + objectRemover.add(Spaces.space1.id, alertId, 'rule', 'alerting'); await waitForStatus(alertId, new Set(['error'])); @@ -264,11 +268,11 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon } const response = await supertest.get( - `${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${id}` + `${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule/${id}` ); expect(response.status).to.eql(200); - const { executionStatus } = response.body || {}; + const { execution_status: executionStatus } = response.body || {}; const { status } = executionStatus || {}; const message = `waitForStatus(${Array.from(statuses)}): got ${JSON.stringify( @@ -300,7 +304,8 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon const response = await supertest.get(`${getUrlPrefix(Spaces.space1.id)}/${findUri}`); expect(response.status).to.eql(200); - const { executionStatus } = response.body.data.find((obj: any) => obj.id === id) || {}; + const { execution_status: executionStatus } = + response.body.data.find((obj: any) => obj.id === id) || {}; const { status } = executionStatus || {}; const message = `waitForFindStatus(${Array.from(statuses)}): got ${JSON.stringify( @@ -320,12 +325,12 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon async function ensureAlertUpdatedAtHasNotChanged(alertId: string, originalUpdatedAt: string) { const response = await supertest.get( - `${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${alertId}` + `${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule/${alertId}` ); - const { updatedAt, executionStatus } = response.body; + const { updated_at: updatedAt, execution_status: executionStatus } = response.body; expect(Date.parse(updatedAt)).to.be.greaterThan(0); expect(Date.parse(updatedAt)).to.eql(Date.parse(originalUpdatedAt)); - expect(Date.parse(executionStatus.lastExecutionDate)).to.be.greaterThan( + expect(Date.parse(executionStatus.last_execution_date)).to.be.greaterThan( Date.parse(originalUpdatedAt) ); } @@ -335,7 +340,7 @@ function expectErrorExecutionStatus(executionStatus: Record, startD expect(executionStatus).to.be.ok(); expect(executionStatus.status).to.equal('error'); - const statusDate = Date.parse(executionStatus.lastExecutionDate); + const statusDate = Date.parse(executionStatus.last_execution_date); const stopDate = Date.now(); expect(startDate).to.be.lessThan(statusDate); expect(stopDate).to.be.greaterThan(statusDate); @@ -345,7 +350,7 @@ function expectErrorExecutionStatus(executionStatus: Record, startD } function getFindUri(filter: string) { - return `api/alerts/_find?filter=alert.attributes.executionStatus.${filter}`; + return `api/alerting/rules/_find?filter=alert.attributes.executionStatus.${filter}`; } function trues(length: number): boolean[] { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/find.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/find.ts index 1493c99162bf5c..191bcb1bb52f0c 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/find.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/find.ts @@ -21,76 +21,76 @@ export default function createFindTests({ getService }: FtrProviderContext) { async function createAlert(overwrites = {}) { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send(getTestAlertData(overwrites)) .expect(200); - objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'rule', 'alerting'); return createdAlert; } it('should handle find alert request appropriately', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'rule', 'alerting'); const response = await supertest.get( `${getUrlPrefix( Spaces.space1.id - )}/api/alerts/_find?search=test.noop&search_fields=alertTypeId` + )}/api/alerting/rules/_find?search=test.noop&search_fields=alertTypeId` ); expect(response.status).to.eql(200); expect(response.body.page).to.equal(1); - expect(response.body.perPage).to.be.greaterThan(0); + expect(response.body.per_page).to.be.greaterThan(0); expect(response.body.total).to.be.greaterThan(0); const match = response.body.data.find((obj: any) => obj.id === createdAlert.id); expect(match).to.eql({ id: createdAlert.id, name: 'abc', tags: ['foo'], - alertTypeId: 'test.noop', + rule_type_id: 'test.noop', consumer: 'alertsFixture', schedule: { interval: '1m' }, enabled: true, actions: [], params: {}, - createdBy: null, - apiKeyOwner: null, - scheduledTaskId: match.scheduledTaskId, - updatedBy: null, + created_by: null, + api_key_owner: null, + scheduled_task_id: match.scheduled_task_id, + updated_by: null, throttle: '1m', - notifyWhen: 'onThrottleInterval', - muteAll: false, - mutedInstanceIds: [], - createdAt: match.createdAt, - updatedAt: match.updatedAt, - executionStatus: match.executionStatus, + notify_when: 'onThrottleInterval', + mute_all: false, + muted_alert_ids: [], + created_at: match.created_at, + updated_at: match.updated_at, + execution_status: match.execution_status, }); - expect(Date.parse(match.createdAt)).to.be.greaterThan(0); - expect(Date.parse(match.updatedAt)).to.be.greaterThan(0); + expect(Date.parse(match.created_at)).to.be.greaterThan(0); + expect(Date.parse(match.updated_at)).to.be.greaterThan(0); }); it(`shouldn't find alert from another space`, async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'rule', 'alerting'); await supertest .get( `${getUrlPrefix( Spaces.other.id - )}/api/alerts/_find?search=test.noop&search_fields=alertTypeId` + )}/api/alerting/rules/_find?search=test.noop&search_fields=alertTypeId` ) .expect(200, { page: 1, - perPage: 10, + per_page: 10, total: 0, data: [], }); @@ -106,12 +106,59 @@ export default function createFindTests({ getService }: FtrProviderContext) { const response = await supertest.get( `${getUrlPrefix( Spaces.space1.id - )}/api/alerts/_find?filter=alert.attributes.params.strValue:"my b"` + )}/api/alerting/rules/_find?filter=alert.attributes.params.strValue:"my b"` ); expect(response.status).to.eql(200); expect(response.body.total).to.equal(1); expect(response.body.data[0].params.strValue).to.eql('my b'); }); + + describe('legacy', () => { + it('should handle find alert request appropriately', async () => { + const { body: createdAlert } = await supertest + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send(getTestAlertData()) + .expect(200); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'rule', 'alerting'); + + const response = await supertest.get( + `${getUrlPrefix( + Spaces.space1.id + )}/api/alerts/_find?search=test.noop&search_fields=alertTypeId` + ); + + expect(response.status).to.eql(200); + expect(response.body.page).to.equal(1); + expect(response.body.perPage).to.be.greaterThan(0); + expect(response.body.total).to.be.greaterThan(0); + const match = response.body.data.find((obj: any) => obj.id === createdAlert.id); + expect(match).to.eql({ + id: createdAlert.id, + name: 'abc', + tags: ['foo'], + alertTypeId: 'test.noop', + consumer: 'alertsFixture', + schedule: { interval: '1m' }, + enabled: true, + actions: [], + params: {}, + createdBy: null, + apiKeyOwner: null, + scheduledTaskId: match.scheduledTaskId, + updatedBy: null, + throttle: '1m', + notifyWhen: 'onThrottleInterval', + muteAll: false, + mutedInstanceIds: [], + createdAt: match.createdAt, + updatedAt: match.updatedAt, + executionStatus: match.executionStatus, + }); + expect(Date.parse(match.createdAt)).to.be.greaterThan(0); + expect(Date.parse(match.updatedAt)).to.be.greaterThan(0); + }); + }); }); } diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get.ts index 3846e35f34fb1c..5245488dcb7b43 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get.ts @@ -21,14 +21,14 @@ export default function createGetTests({ getService }: FtrProviderContext) { it('should handle get alert request appropriately', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'rule', 'alerting'); const response = await supertest.get( - `${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}` + `${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule/${createdAlert.id}` ); expect(response.status).to.eql(200); @@ -36,38 +36,38 @@ export default function createGetTests({ getService }: FtrProviderContext) { id: createdAlert.id, name: 'abc', tags: ['foo'], - alertTypeId: 'test.noop', + rule_type_id: 'test.noop', consumer: 'alertsFixture', schedule: { interval: '1m' }, enabled: true, actions: [], params: {}, - createdBy: null, - scheduledTaskId: response.body.scheduledTaskId, - updatedBy: null, - apiKeyOwner: null, + created_by: null, + scheduled_task_id: response.body.scheduled_task_id, + updated_by: null, + api_key_owner: null, throttle: '1m', - notifyWhen: 'onThrottleInterval', - muteAll: false, - mutedInstanceIds: [], - createdAt: response.body.createdAt, - updatedAt: response.body.updatedAt, - executionStatus: response.body.executionStatus, + notify_when: 'onThrottleInterval', + mute_all: false, + muted_alert_ids: [], + created_at: response.body.created_at, + updated_at: response.body.updated_at, + execution_status: response.body.execution_status, }); - expect(Date.parse(response.body.createdAt)).to.be.greaterThan(0); - expect(Date.parse(response.body.updatedAt)).to.be.greaterThan(0); + expect(Date.parse(response.body.created_at)).to.be.greaterThan(0); + expect(Date.parse(response.body.updated_at)).to.be.greaterThan(0); }); it(`shouldn't find alert from another space`, async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'rule', 'alerting'); await supertest - .get(`${getUrlPrefix(Spaces.other.id)}/api/alerts/alert/${createdAlert.id}`) + .get(`${getUrlPrefix(Spaces.other.id)}/api/alerting/rule/${createdAlert.id}`) .expect(404, { statusCode: 404, error: 'Not Found', @@ -76,11 +76,52 @@ export default function createGetTests({ getService }: FtrProviderContext) { }); it(`should handle get alert request appropriately when alert doesn't exist`, async () => { - await supertest.get(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/1`).expect(404, { + await supertest.get(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule/1`).expect(404, { statusCode: 404, error: 'Not Found', message: 'Saved object [alert/1] not found', }); }); + + describe('legacy', () => { + it('should handle get alert request appropriately', async () => { + const { body: createdAlert } = await supertest + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send(getTestAlertData()) + .expect(200); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'rule', 'alerting'); + + const response = await supertest.get( + `${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}` + ); + + expect(response.status).to.eql(200); + expect(response.body).to.eql({ + id: createdAlert.id, + name: 'abc', + tags: ['foo'], + alertTypeId: 'test.noop', + consumer: 'alertsFixture', + schedule: { interval: '1m' }, + enabled: true, + actions: [], + params: {}, + createdBy: null, + scheduledTaskId: response.body.scheduledTaskId, + updatedBy: null, + apiKeyOwner: null, + throttle: '1m', + notifyWhen: 'onThrottleInterval', + muteAll: false, + mutedInstanceIds: [], + createdAt: response.body.createdAt, + updatedAt: response.body.updatedAt, + executionStatus: response.body.executionStatus, + }); + expect(Date.parse(response.body.createdAt)).to.be.greaterThan(0); + expect(Date.parse(response.body.updatedAt)).to.be.greaterThan(0); + }); + }); }); } diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get_alert_instance_summary.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get_alert_instance_summary.ts index 25d9efdebdfb0c..099502e375faa1 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get_alert_instance_summary.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get_alert_instance_summary.ts @@ -32,7 +32,7 @@ export default function createGetAlertInstanceSummaryTests({ getService }: FtrPr it(`handles non-existant alert`, async () => { await supertest - .get(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/1/_instance_summary`) + .get(`${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rule/1/_alert_summary`) .expect(404, { statusCode: 404, error: 'Not Found', @@ -42,106 +42,106 @@ export default function createGetAlertInstanceSummaryTests({ getService }: FtrPr it('handles no-op alert', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'rule', 'alerting'); await waitForEvents(createdAlert.id, ['execute']); const response = await supertest.get( - `${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}/_instance_summary` + `${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rule/${createdAlert.id}/_alert_summary` ); expect(response.status).to.eql(200); - const { statusStartDate, statusEndDate } = response.body; + const { status_start_date: statusStartDate, status_end_date: statusEndDate } = response.body; expect(Date.parse(statusStartDate)).to.be.lessThan(Date.parse(statusEndDate)); - const stableBody = omit(response.body, ['statusStartDate', 'statusEndDate', 'lastRun']); + const stableBody = omit(response.body, ['status_start_date', 'status_end_date', 'last_run']); expect(stableBody).to.eql({ id: createdAlert.id, name: 'abc', tags: ['foo'], - alertTypeId: 'test.noop', + rule_type_id: 'test.noop', consumer: 'alertsFixture', status: 'OK', - muteAll: false, + mute_all: false, throttle: '1m', enabled: true, - errorMessages: [], - instances: {}, + error_messages: [], + alerts: {}, }); }); it('handles no-op alert without waiting for execution event', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'rule', 'alerting'); const response = await supertest.get( - `${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}/_instance_summary` + `${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rule/${createdAlert.id}/_alert_summary` ); expect(response.status).to.eql(200); - const { statusStartDate, statusEndDate } = response.body; + const { status_start_date: statusStartDate, status_end_date: statusEndDate } = response.body; expect(Date.parse(statusStartDate)).to.be.lessThan(Date.parse(statusEndDate)); - const stableBody = omit(response.body, ['statusStartDate', 'statusEndDate', 'lastRun']); + const stableBody = omit(response.body, ['status_start_date', 'status_end_date', 'last_run']); expect(stableBody).to.eql({ id: createdAlert.id, name: 'abc', tags: ['foo'], - alertTypeId: 'test.noop', + rule_type_id: 'test.noop', consumer: 'alertsFixture', status: 'OK', - muteAll: false, + mute_all: false, throttle: '1m', enabled: true, - errorMessages: [], - instances: {}, + error_messages: [], + alerts: {}, }); }); it('handles dateStart parameter', async () => { const dateStart = '2020-08-08T08:08:08.008Z'; const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'rule', 'alerting'); await waitForEvents(createdAlert.id, ['execute']); const response = await supertest.get( - `${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${ + `${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rule/${ createdAlert.id - }/_instance_summary?dateStart=${dateStart}` + }/_alert_summary?date_start=${dateStart}` ); expect(response.status).to.eql(200); - const { statusStartDate, statusEndDate } = response.body; + const { status_start_date: statusStartDate, status_end_date: statusEndDate } = response.body; expect(Date.parse(statusStartDate)).to.be.lessThan(Date.parse(statusEndDate)); expect(statusStartDate).to.be(dateStart); }); it('handles invalid dateStart parameter', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'rule', 'alerting'); await waitForEvents(createdAlert.id, ['execute']); const dateStart = 'X0X0-08-08T08:08:08.008Z'; const response = await supertest.get( - `${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${ + `${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rule/${ createdAlert.id - }/_instance_summary?dateStart=${dateStart}` + }/_alert_summary?date_start=${dateStart}` ); expect(response.status).to.eql(400); expect(response.body).to.eql({ @@ -153,20 +153,20 @@ export default function createGetAlertInstanceSummaryTests({ getService }: FtrPr it('handles muted instances', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'rule', 'alerting'); await alertUtils.muteInstance(createdAlert.id, '1'); await waitForEvents(createdAlert.id, ['execute']); const response = await supertest.get( - `${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}/_instance_summary` + `${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rule/${createdAlert.id}/_alert_summary` ); expect(response.status).to.eql(200); - expect(response.body.instances).to.eql({ + expect(response.body.alerts).to.eql({ '1': { status: 'OK', muted: true, @@ -177,17 +177,17 @@ export default function createGetAlertInstanceSummaryTests({ getService }: FtrPr it('handles alert errors', async () => { const dateNow = Date.now(); const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') - .send(getTestAlertData({ alertTypeId: 'test.throw' })) + .send(getTestAlertData({ rule_type_id: 'test.throw' })) .expect(200); - objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'rule', 'alerting'); await waitForEvents(createdAlert.id, ['execute']); const response = await supertest.get( - `${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}/_instance_summary` + `${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rule/${createdAlert.id}/_alert_summary` ); - const { errorMessages } = response.body; + const { error_messages: errorMessages } = response.body; expect(errorMessages.length).to.be.greaterThan(0); const errorMessage = errorMessages[0]; expect(Date.parse(errorMessage.date)).to.be.greaterThan(dateNow); @@ -203,26 +203,26 @@ export default function createGetAlertInstanceSummaryTests({ getService }: FtrPr }; const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ - alertTypeId: 'test.patternFiring', + rule_type_id: 'test.patternFiring', params: { pattern }, schedule: { interval: '1s' }, }) ) .expect(200); - objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'rule', 'alerting'); await alertUtils.muteInstance(createdAlert.id, 'instanceC'); await alertUtils.muteInstance(createdAlert.id, 'instanceD'); await waitForEvents(createdAlert.id, ['new-instance', 'recovered-instance']); const response = await supertest.get( - `${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}/_instance_summary` + `${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rule/${createdAlert.id}/_alert_summary` ); - const actualInstances = response.body.instances; + const actualInstances = response.body.alerts; const expectedInstances = { instanceA: { status: 'Active', @@ -247,6 +247,62 @@ export default function createGetAlertInstanceSummaryTests({ getService }: FtrPr }; expect(actualInstances).to.eql(expectedInstances); }); + + describe('legacy', () => { + it('handles multi-instance status', async () => { + // pattern of when the alert should fire + const pattern = { + instanceA: [true, true, true, true], + instanceB: [true, true, false, false], + instanceC: [true, true, true, true], + }; + + const { body: createdAlert } = await supertest + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send( + getTestAlertData({ + rule_type_id: 'test.patternFiring', + params: { pattern }, + schedule: { interval: '1s' }, + }) + ) + .expect(200); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'rule', 'alerting'); + + await alertUtils.muteInstance(createdAlert.id, 'instanceC'); + await alertUtils.muteInstance(createdAlert.id, 'instanceD'); + await waitForEvents(createdAlert.id, ['new-instance', 'recovered-instance']); + const response = await supertest.get( + `${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}/_instance_summary` + ); + + const actualInstances = response.body.instances; + const expectedInstances = { + instanceA: { + status: 'Active', + muted: false, + actionGroupId: 'default', + activeStartDate: actualInstances.instanceA.activeStartDate, + }, + instanceB: { + status: 'OK', + muted: false, + }, + instanceC: { + status: 'Active', + muted: true, + actionGroupId: 'default', + activeStartDate: actualInstances.instanceC.activeStartDate, + }, + instanceD: { + status: 'OK', + muted: true, + }, + }; + expect(actualInstances).to.eql(expectedInstances); + }); + }); }); async function waitForEvents(id: string, actions: string[]) { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get_alert_state.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get_alert_state.ts index e3a23e499149bf..9154c85af1bc7a 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get_alert_state.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get_alert_state.ts @@ -22,57 +22,58 @@ export default function createGetAlertStateTests({ getService }: FtrProviderCont it('should handle getAlertState request appropriately', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'rule', 'alerting'); const response = await supertest.get( - `${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}/state` + `${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rule/${createdAlert.id}/state` ); expect(response.status).to.eql(200); - expect(response.body).to.key('alertInstances', 'previousStartedAt'); + expect(response.body).to.key('alerts', 'previous_started_at'); }); it('should fetch updated state', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send({ enabled: true, name: 'abc', tags: ['foo'], - alertTypeId: 'test.cumulative-firing', + rule_type_id: 'test.cumulative-firing', consumer: 'alertsFixture', schedule: { interval: '5s' }, throttle: '5s', actions: [], params: {}, + notify_when: 'onThrottleInterval', }) .expect(200); - objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'rule', 'alerting'); // wait for alert to actually execute await retry.try(async () => { const response = await supertest.get( - `${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}/state` + `${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rule/${createdAlert.id}/state` ); expect(response.status).to.eql(200); - expect(response.body).to.key('alertInstances', 'alertTypeState', 'previousStartedAt'); - expect(response.body.alertTypeState.runCount).to.greaterThan(1); + expect(response.body).to.key('alerts', 'rule_type_state', 'previous_started_at'); + expect(response.body.rule_type_state.runCount).to.greaterThan(1); }); const response = await supertest.get( - `${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}/state` + `${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rule/${createdAlert.id}/state` ); - expect(response.body.alertTypeState.runCount).to.greaterThan(0); + expect(response.body.rule_type_state.runCount).to.greaterThan(0); - const alertInstances = Object.entries>(response.body.alertInstances); - expect(alertInstances.length).to.eql(response.body.alertTypeState.runCount); + const alertInstances = Object.entries>(response.body.alerts); + expect(alertInstances.length).to.eql(response.body.rule_type_state.runCount); alertInstances.forEach(([key, value], index) => { expect(key).to.eql(`instance-${index}`); expect(value.state).to.eql({ instanceStateValue: true }); @@ -81,12 +82,58 @@ export default function createGetAlertStateTests({ getService }: FtrProviderCont it(`should handle getAlertState request appropriately when alert doesn't exist`, async () => { await supertest - .get(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/1/state`) + .get(`${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rule/1/state`) .expect(404, { statusCode: 404, error: 'Not Found', message: 'Saved object [alert/1] not found', }); }); + + describe('legacy', () => { + it('should fetch updated state', async () => { + const { body: createdAlert } = await supertest + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send({ + enabled: true, + name: 'abc', + tags: ['foo'], + rule_type_id: 'test.cumulative-firing', + consumer: 'alertsFixture', + schedule: { interval: '5s' }, + throttle: '5s', + actions: [], + params: {}, + notify_when: 'onThrottleInterval', + }) + .expect(200); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'rule', 'alerting'); + + // wait for alert to actually execute + await retry.try(async () => { + const response = await supertest.get( + `${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}/state` + ); + + expect(response.status).to.eql(200); + expect(response.body).to.key('alertInstances', 'alertTypeState', 'previousStartedAt'); + expect(response.body.alertTypeState.runCount).to.greaterThan(1); + }); + + const response = await supertest.get( + `${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rule/${createdAlert.id}/state` + ); + + expect(response.body.rule_type_state.runCount).to.greaterThan(0); + + const alertInstances = Object.entries>(response.body.alerts); + expect(alertInstances.length).to.eql(response.body.rule_type_state.runCount); + alertInstances.forEach(([key, value], index) => { + expect(key).to.eql(`instance-${index}`); + expect(value.state).to.eql({ instanceStateValue: true }); + }); + }); + }); }); } diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/index.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/index.ts index c1050d26e083ba..e9aeec1717c968 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/index.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/index.ts @@ -23,7 +23,7 @@ export default function alertingTests({ loadTestFile, getService }: FtrProviderC loadTestFile(require.resolve('./get')); loadTestFile(require.resolve('./get_alert_state')); loadTestFile(require.resolve('./get_alert_instance_summary')); - loadTestFile(require.resolve('./list_alert_types')); + loadTestFile(require.resolve('./rule_types')); loadTestFile(require.resolve('./event_log')); loadTestFile(require.resolve('./execution_status')); loadTestFile(require.resolve('./mute_all')); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/migrations.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/migrations.ts index 3954dbdb337a36..30e69d62933e03 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/migrations.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/migrations.ts @@ -25,7 +25,7 @@ export default function createGetTests({ getService }: FtrProviderContext) { it('7.10.0 migrates the `alerting` consumer to be the `alerts`', async () => { const response = await supertest.get( - `${getUrlPrefix(``)}/api/alerts/alert/74f3e6d7-b7bb-477d-ac28-92ee22728e6e` + `${getUrlPrefix(``)}/api/alerting/rule/74f3e6d7-b7bb-477d-ac28-92ee22728e6e` ); expect(response.status).to.eql(200); @@ -34,7 +34,7 @@ export default function createGetTests({ getService }: FtrProviderContext) { it('7.10.0 migrates the `metrics` consumer to be the `infrastructure`', async () => { const response = await supertest.get( - `${getUrlPrefix(``)}/api/alerts/alert/74f3e6d7-b7bb-477d-ac28-fdf248d5f2a4` + `${getUrlPrefix(``)}/api/alerting/rule/74f3e6d7-b7bb-477d-ac28-fdf248d5f2a4` ); expect(response.status).to.eql(200); @@ -43,14 +43,14 @@ export default function createGetTests({ getService }: FtrProviderContext) { it('7.10.0 migrates PagerDuty actions to have a default dedupKey', async () => { const response = await supertest.get( - `${getUrlPrefix(``)}/api/alerts/alert/b6087f72-994f-46fb-8120-c6e5c50d0f8f` + `${getUrlPrefix(``)}/api/alerting/rule/b6087f72-994f-46fb-8120-c6e5c50d0f8f` ); expect(response.status).to.eql(200); expect(response.body.actions).to.eql([ { - actionTypeId: '.pagerduty', + connector_type_id: '.pagerduty', id: 'a6a8ab7a-35cf-445e-ade3-215a029c2ee3', group: 'default', params: { @@ -60,7 +60,7 @@ export default function createGetTests({ getService }: FtrProviderContext) { }, }, { - actionTypeId: '.pagerduty', + connector_type_id: '.pagerduty', id: 'a6a8ab7a-35cf-445e-ade3-215a029c2ee3', group: 'default', params: { @@ -71,7 +71,7 @@ export default function createGetTests({ getService }: FtrProviderContext) { }, }, { - actionTypeId: '.pagerduty', + connector_type_id: '.pagerduty', id: 'a6a8ab7a-35cf-445e-ade3-215a029c2ee3', group: 'default', params: { @@ -86,32 +86,32 @@ export default function createGetTests({ getService }: FtrProviderContext) { it('7.11.0 migrates alerts to contain `updatedAt` field', async () => { const response = await supertest.get( - `${getUrlPrefix(``)}/api/alerts/alert/74f3e6d7-b7bb-477d-ac28-92ee22728e6e` + `${getUrlPrefix(``)}/api/alerting/rule/74f3e6d7-b7bb-477d-ac28-92ee22728e6e` ); expect(response.status).to.eql(200); - expect(response.body.updatedAt).to.eql('2020-06-17T15:35:39.839Z'); + expect(response.body.updated_at).to.eql('2020-06-17T15:35:39.839Z'); }); it('7.11.0 migrates alerts to contain `notifyWhen` field', async () => { const response = await supertest.get( - `${getUrlPrefix(``)}/api/alerts/alert/74f3e6d7-b7bb-477d-ac28-92ee22728e6e` + `${getUrlPrefix(``)}/api/alerting/rule/74f3e6d7-b7bb-477d-ac28-92ee22728e6e` ); expect(response.status).to.eql(200); - expect(response.body.notifyWhen).to.eql('onActiveAlert'); + expect(response.body.notify_when).to.eql('onActiveAlert'); }); it('7.11.2 migrates alerts with case actions, case fields are nested in an incident object', async () => { const response = await supertest.get( - `${getUrlPrefix(``)}/api/alerts/alert/99f3e6d7-b7bb-477d-ac28-92ee22726969` + `${getUrlPrefix(``)}/api/alerting/rule/99f3e6d7-b7bb-477d-ac28-92ee22726969` ); expect(response.status).to.eql(200); expect(response.body.actions).to.eql([ { id: '66a8ab7a-35cf-445e-ade3-215a029c6969', - actionTypeId: '.servicenow', + connector_type_id: '.servicenow', group: 'threshold met', params: { subAction: 'pushToService', @@ -129,7 +129,7 @@ export default function createGetTests({ getService }: FtrProviderContext) { }, { id: '66a8ab7a-35cf-445e-ade3-215a029c6969', - actionTypeId: '.jira', + connector_type_id: '.jira', group: 'threshold met', params: { subAction: 'pushToService', @@ -153,7 +153,7 @@ export default function createGetTests({ getService }: FtrProviderContext) { }, { id: '66a8ab7a-35cf-445e-ade3-215a029c6969', - actionTypeId: '.resilient', + connector_type_id: '.resilient', group: 'threshold met', params: { subAction: 'pushToService', diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/mustache_templates.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/mustache_templates.ts index ae24994588348c..8d300733bafc37 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/mustache_templates.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/mustache_templates.ts @@ -63,11 +63,11 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon it('should handle escapes in webhook', async () => { const url = formatUrl(new URL(webhookSimulatorURL), { auth: false }); const actionResponse = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/action`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connector`) .set('kbn-xsrf', 'test') .send({ name: 'testing mustache escapes for webhook', - actionTypeId: '.webhook', + connector_type_id: '.webhook', secrets: {}, config: { headers: { @@ -78,19 +78,19 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon }); expect(actionResponse.status).to.eql(200); const createdAction = actionResponse.body; - objectRemover.add(Spaces.space1.id, createdAction.id, 'action', 'actions'); + objectRemover.add(Spaces.space1.id, createdAction.id, 'connector', 'actions'); // from x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/alert_types.ts, // const EscapableStrings const varsTemplate = '{{context.escapableDoubleQuote}} -- {{context.escapableLineFeed}}'; const alertResponse = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ name: 'testing variable escapes for webhook', - alertTypeId: 'test.patternFiring', + rule_type_id: 'test.patternFiring', params: { pattern: { instance: [true] }, }, @@ -107,7 +107,7 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon ); expect(alertResponse.status).to.eql(200); const createdAlert = alertResponse.body; - objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'rule', 'alerting'); const body = await retry.try(async () => waitForActionBody(webhookSimulatorURL, createdAlert.id) @@ -117,18 +117,18 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon it('should handle escapes in slack', async () => { const actionResponse = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/action`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connector`) .set('kbn-xsrf', 'test') .send({ name: "testing backtic'd mustache escapes for slack", - actionTypeId: '.slack', + connector_type_id: '.slack', secrets: { webhookUrl: slackSimulatorURL, }, }); expect(actionResponse.status).to.eql(200); const createdAction = actionResponse.body; - objectRemover.add(Spaces.space1.id, createdAction.id, 'action', 'actions'); + objectRemover.add(Spaces.space1.id, createdAction.id, 'connector', 'actions'); // from x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/alert_types.ts, // const EscapableStrings @@ -136,12 +136,12 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon '{{context.escapableBacktic}} -- {{context.escapableBold}} -- {{context.escapableBackticBold}} -- {{context.escapableHtml}}'; const alertResponse = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ name: 'testing variable escapes for slack', - alertTypeId: 'test.patternFiring', + rule_type_id: 'test.patternFiring', params: { pattern: { instance: [true] }, }, @@ -158,7 +158,7 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon ); expect(alertResponse.status).to.eql(200); const createdAlert = alertResponse.body; - objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'rule', 'alerting'); const body = await retry.try(async () => waitForActionBody(slackSimulatorURL, createdAlert.id) @@ -168,30 +168,30 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon it('should handle context variable object expansion', async () => { const actionResponse = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/action`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connector`) .set('kbn-xsrf', 'test') .send({ name: 'testing context variable expansion', - actionTypeId: '.slack', + connector_type_id: '.slack', secrets: { webhookUrl: slackSimulatorURL, }, }); expect(actionResponse.status).to.eql(200); const createdAction = actionResponse.body; - objectRemover.add(Spaces.space1.id, createdAction.id, 'action', 'actions'); + objectRemover.add(Spaces.space1.id, createdAction.id, 'connector', 'actions'); // from x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/alert_types.ts, // const DeepContextVariables const varsTemplate = '{{context.deep}}'; const alertResponse = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ name: 'testing context variable expansion', - alertTypeId: 'test.patternFiring', + rule_type_id: 'test.patternFiring', params: { pattern: { instance: [true, true] }, }, @@ -208,7 +208,7 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon ); expect(alertResponse.status).to.eql(200); const createdAlert = alertResponse.body; - objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'rule', 'alerting'); const body = await retry.try(async () => waitForActionBody(slackSimulatorURL, createdAlert.id) @@ -220,28 +220,28 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon it('should render kibanaBaseUrl as empty string since not configured', async () => { const actionResponse = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/action`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connector`) .set('kbn-xsrf', 'test') .send({ name: 'testing context variable expansion', - actionTypeId: '.slack', + connector_type_id: '.slack', secrets: { webhookUrl: slackSimulatorURL, }, }); expect(actionResponse.status).to.eql(200); const createdAction = actionResponse.body; - objectRemover.add(Spaces.space1.id, createdAction.id, 'action', 'actions'); + objectRemover.add(Spaces.space1.id, createdAction.id, 'connector', 'actions'); const varsTemplate = 'kibanaBaseUrl: "{{kibanaBaseUrl}}"'; const alertResponse = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ name: 'testing context variable kibanaBaseUrl', - alertTypeId: 'test.patternFiring', + rule_type_id: 'test.patternFiring', params: { pattern: { instance: [true, true] }, }, @@ -258,7 +258,7 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon ); expect(alertResponse.status).to.eql(200); const createdAlert = alertResponse.body; - objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'rule', 'alerting'); const body = await retry.try(async () => waitForActionBody(slackSimulatorURL, createdAlert.id) @@ -269,11 +269,11 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon it('should render action variables in rule action', async () => { const url = formatUrl(new URL(webhookSimulatorURL), { auth: false }); const actionResponse = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/action`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connector`) .set('kbn-xsrf', 'test') .send({ name: 'testing action variable rendering', - actionTypeId: '.webhook', + connector_type_id: '.webhook', secrets: {}, config: { headers: { @@ -284,15 +284,15 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon }); expect(actionResponse.status).to.eql(200); const createdAction = actionResponse.body; - objectRemover.add(Spaces.space1.id, createdAction.id, 'action', 'actions'); + objectRemover.add(Spaces.space1.id, createdAction.id, 'connector', 'actions'); const alertResponse = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ name: 'testing variable escapes for webhook', - alertTypeId: 'test.patternFiring', + rule_type_id: 'test.patternFiring', params: { pattern: { instance: [true] }, }, @@ -309,7 +309,7 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon ); expect(alertResponse.status).to.eql(200); const createdAlert = alertResponse.body; - objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'rule', 'alerting'); const body = await retry.try(async () => waitForActionBody(webhookSimulatorURL, createdAlert.id) diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/mute_all.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/mute_all.ts index feac0849a28646..c21a13edbf2cb3 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/mute_all.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/mute_all.ts @@ -28,19 +28,19 @@ export default function createMuteTests({ getService }: FtrProviderContext) { it('should handle mute alert request appropriately', async () => { const { body: createdAlert } = await supertestWithoutAuth - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send(getTestAlertData({ enabled: false })) .expect(200); - objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'rule', 'alerting'); await alertUtils.muteAll(createdAlert.id); const { body: updatedAlert } = await supertestWithoutAuth - .get(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}`) + .get(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .expect(200); - expect(updatedAlert.muteAll).to.eql(true); + expect(updatedAlert.mute_all).to.eql(true); // Ensure AAD isn't broken await checkAAD({ @@ -50,5 +50,35 @@ export default function createMuteTests({ getService }: FtrProviderContext) { id: createdAlert.id, }); }); + + describe('legacy', () => { + it('should handle mute alert request appropriately', async () => { + const { body: createdAlert } = await supertestWithoutAuth + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send(getTestAlertData({ enabled: false })) + .expect(200); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'rule', 'alerting'); + + await supertestWithoutAuth + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}/_mute_all`) + .set('kbn-xsrf', 'foo') + .expect(204); + + const { body: updatedAlert } = await supertestWithoutAuth + .get(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule/${createdAlert.id}`) + .set('kbn-xsrf', 'foo') + .expect(200); + expect(updatedAlert.mute_all).to.eql(true); + + // Ensure AAD isn't broken + await checkAAD({ + supertest: supertestWithoutAuth, + spaceId: Spaces.space1.id, + type: 'alert', + id: createdAlert.id, + }); + }); + }); }); } diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/mute_instance.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/mute_instance.ts index 0205485e322eaa..afe29280748a5a 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/mute_instance.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/mute_instance.ts @@ -28,19 +28,19 @@ export default function createMuteInstanceTests({ getService }: FtrProviderConte it('should handle mute alert instance request appropriately', async () => { const { body: createdAlert } = await supertestWithoutAuth - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send(getTestAlertData({ enabled: false })) .expect(200); - objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'rule', 'alerting'); await alertUtils.muteInstance(createdAlert.id, '1'); const { body: updatedAlert } = await supertestWithoutAuth - .get(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}`) + .get(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .expect(200); - expect(updatedAlert.mutedInstanceIds).to.eql(['1']); + expect(updatedAlert.muted_alert_ids).to.eql(['1']); // Ensure AAD isn't broken await checkAAD({ @@ -50,5 +50,39 @@ export default function createMuteInstanceTests({ getService }: FtrProviderConte id: createdAlert.id, }); }); + + describe('legacy', () => { + it('should handle mute alert instance request appropriately', async () => { + const { body: createdAlert } = await supertestWithoutAuth + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send(getTestAlertData({ enabled: false })) + .expect(200); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'rule', 'alerting'); + + await supertestWithoutAuth + .post( + `${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${ + createdAlert.id + }/alert_instance/1/_mute` + ) + .set('kbn-xsrf', 'foo') + .expect(204); + + const { body: updatedAlert } = await supertestWithoutAuth + .get(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule/${createdAlert.id}`) + .set('kbn-xsrf', 'foo') + .expect(200); + expect(updatedAlert.muted_alert_ids).to.eql(['1']); + + // Ensure AAD isn't broken + await checkAAD({ + supertest: supertestWithoutAuth, + spaceId: Spaces.space1.id, + type: 'alert', + id: createdAlert.id, + }); + }); + }); }); } diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/notify_when.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/notify_when.ts index 29720f8d60ff52..7f1b82614a1000 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/notify_when.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/notify_when.ts @@ -24,22 +24,22 @@ export default function createNotifyWhenTests({ getService }: FtrProviderContext it(`alert with notifyWhen=onActiveAlert should always execute actions `, async () => { const { body: defaultAction } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/action`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connector`) .set('kbn-xsrf', 'foo') .send({ name: 'My Default Action', - actionTypeId: 'test.noop', + connector_type_id: 'test.noop', config: {}, secrets: {}, }) .expect(200); const { body: recoveredAction } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/action`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connector`) .set('kbn-xsrf', 'foo') .send({ name: 'My Recovered Action', - actionTypeId: 'test.noop', + connector_type_id: 'test.noop', config: {}, secrets: {}, }) @@ -52,15 +52,15 @@ export default function createNotifyWhenTests({ getService }: FtrProviderContext active ? 'default' : 'recovered' ); const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ - alertTypeId: 'test.patternFiring', + rule_type_id: 'test.patternFiring', params: { pattern }, schedule: { interval: '1s' }, throttle: null, - notifyWhen: 'onActiveAlert', + notify_when: 'onActiveAlert', actions: [ { id: defaultAction.id, @@ -76,7 +76,7 @@ export default function createNotifyWhenTests({ getService }: FtrProviderContext }) ) .expect(200); - objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'rule', 'alerting'); const events = await retry.try(async () => { return await getEventLog({ @@ -101,22 +101,22 @@ export default function createNotifyWhenTests({ getService }: FtrProviderContext it(`alert with notifyWhen=onActionGroupChange should execute actions when action group changes`, async () => { const { body: defaultAction } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/action`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connector`) .set('kbn-xsrf', 'foo') .send({ name: 'My Default Action', - actionTypeId: 'test.noop', + connector_type_id: 'test.noop', config: {}, secrets: {}, }) .expect(200); const { body: recoveredAction } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/action`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connector`) .set('kbn-xsrf', 'foo') .send({ name: 'My Recovered Action', - actionTypeId: 'test.noop', + connector_type_id: 'test.noop', config: {}, secrets: {}, }) @@ -128,15 +128,15 @@ export default function createNotifyWhenTests({ getService }: FtrProviderContext const expectedActionGroupBasedOnPattern = ['default', 'recovered', 'default', 'recovered']; const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ - alertTypeId: 'test.patternFiring', + rule_type_id: 'test.patternFiring', params: { pattern }, schedule: { interval: '1s' }, throttle: null, - notifyWhen: 'onActionGroupChange', + notify_when: 'onActionGroupChange', actions: [ { id: defaultAction.id, @@ -152,7 +152,7 @@ export default function createNotifyWhenTests({ getService }: FtrProviderContext }) ) .expect(200); - objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'rule', 'alerting'); const events = await retry.try(async () => { return await getEventLog({ @@ -177,22 +177,22 @@ export default function createNotifyWhenTests({ getService }: FtrProviderContext it(`alert with notifyWhen=onActionGroupChange should only execute actions when action subgroup changes`, async () => { const { body: defaultAction } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/action`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connector`) .set('kbn-xsrf', 'foo') .send({ name: 'My Default Action', - actionTypeId: 'test.noop', + connector_type_id: 'test.noop', config: {}, secrets: {}, }) .expect(200); const { body: recoveredAction } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/action`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connector`) .set('kbn-xsrf', 'foo') .send({ name: 'My Recovered Action', - actionTypeId: 'test.noop', + connector_type_id: 'test.noop', config: {}, secrets: {}, }) @@ -219,15 +219,15 @@ export default function createNotifyWhenTests({ getService }: FtrProviderContext ]; const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ - alertTypeId: 'test.patternFiring', + rule_type_id: 'test.patternFiring', params: { pattern }, schedule: { interval: '1s' }, throttle: null, - notifyWhen: 'onActionGroupChange', + notify_when: 'onActionGroupChange', actions: [ { id: defaultAction.id, @@ -243,7 +243,7 @@ export default function createNotifyWhenTests({ getService }: FtrProviderContext }) ) .expect(200); - objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'rule', 'alerting'); const events = await retry.try(async () => { return await getEventLog({ diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/list_alert_types.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/rule_types.ts similarity index 59% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/list_alert_types.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/rule_types.ts index 38e38824ca9d7b..3d3cec4c30252f 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/list_alert_types.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/rule_types.ts @@ -14,42 +14,42 @@ import { FtrProviderContext } from '../../../common/ftr_provider_context'; export default function listAlertTypes({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - describe('list_alert_types', () => { + describe('rule_types', () => { it('should return 200 with list of alert types', async () => { const response = await supertest.get( - `${getUrlPrefix(Spaces.space1.id)}/api/alerts/list_alert_types` + `${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule_types` ); expect(response.status).to.eql(200); - const { authorizedConsumers, ...fixtureAlertType } = response.body.find( + const { authorized_consumers: authorizedConsumers, ...fixtureAlertType } = response.body.find( (alertType: any) => alertType.id === 'test.noop' ); expect(fixtureAlertType).to.eql({ - actionGroups: [ + action_groups: [ { id: 'default', name: 'Default' }, { id: 'recovered', name: 'Recovered' }, ], - defaultActionGroupId: 'default', + default_action_group_id: 'default', id: 'test.noop', name: 'Test: Noop', - actionVariables: { + action_variables: { state: [], params: [], context: [], }, - recoveryActionGroup: { + recovery_action_group: { id: 'recovered', name: 'Recovered', }, producer: 'alertsFixture', - minimumLicenseRequired: 'basic', - enabledInLicense: true, + minimum_license_required: 'basic', + enabled_in_license: true, }); expect(Object.keys(authorizedConsumers)).to.contain('alertsFixture'); }); it('should return actionVariables with both context and state', async () => { const response = await supertest.get( - `${getUrlPrefix(Spaces.space1.id)}/api/alerts/list_alert_types` + `${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule_types` ); expect(response.status).to.eql(200); @@ -57,7 +57,7 @@ export default function listAlertTypes({ getService }: FtrProviderContext) { (alertType: any) => alertType.id === 'test.always-firing' ); - expect(fixtureAlertType.actionVariables).to.eql({ + expect(fixtureAlertType.action_variables).to.eql({ state: [{ name: 'instanceStateValue', description: 'the instance state value' }], params: [{ name: 'instanceParamsValue', description: 'the instance params value' }], context: [{ name: 'instanceContextValue', description: 'the instance context value' }], @@ -66,7 +66,7 @@ export default function listAlertTypes({ getService }: FtrProviderContext) { it('should return actionVariables with just context', async () => { const response = await supertest.get( - `${getUrlPrefix(Spaces.space1.id)}/api/alerts/list_alert_types` + `${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule_types` ); expect(response.status).to.eql(200); @@ -74,7 +74,7 @@ export default function listAlertTypes({ getService }: FtrProviderContext) { (alertType: any) => alertType.id === 'test.onlyContextVariables' ); - expect(fixtureAlertType.actionVariables).to.eql({ + expect(fixtureAlertType.action_variables).to.eql({ state: [], params: [], context: [{ name: 'aContextVariable', description: 'this is a context variable' }], @@ -83,7 +83,7 @@ export default function listAlertTypes({ getService }: FtrProviderContext) { it('should return actionVariables with just state', async () => { const response = await supertest.get( - `${getUrlPrefix(Spaces.space1.id)}/api/alerts/list_alert_types` + `${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule_types` ); expect(response.status).to.eql(200); @@ -91,11 +91,45 @@ export default function listAlertTypes({ getService }: FtrProviderContext) { (alertType: any) => alertType.id === 'test.onlyStateVariables' ); - expect(fixtureAlertType.actionVariables).to.eql({ + expect(fixtureAlertType.action_variables).to.eql({ state: [{ name: 'aStateVariable', description: 'this is a state variable' }], context: [], params: [], }); }); + + describe('legacy', () => { + it('should return 200 with list of alert types', async () => { + const response = await supertest.get( + `${getUrlPrefix(Spaces.space1.id)}/api/alerts/list_alert_types` + ); + expect(response.status).to.eql(200); + const { authorizedConsumers, ...fixtureAlertType } = response.body.find( + (alertType: any) => alertType.id === 'test.noop' + ); + expect(fixtureAlertType).to.eql({ + actionGroups: [ + { id: 'default', name: 'Default' }, + { id: 'recovered', name: 'Recovered' }, + ], + defaultActionGroupId: 'default', + id: 'test.noop', + name: 'Test: Noop', + actionVariables: { + state: [], + params: [], + context: [], + }, + recoveryActionGroup: { + id: 'recovered', + name: 'Recovered', + }, + producer: 'alertsFixture', + minimumLicenseRequired: 'basic', + enabledInLicense: true, + }); + expect(Object.keys(authorizedConsumers)).to.contain('alertsFixture'); + }); + }); }); } diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/unmute_all.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/unmute_all.ts index 13b021474e01eb..2fffa9189e0ad7 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/unmute_all.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/unmute_all.ts @@ -28,20 +28,20 @@ export default function createUnmuteTests({ getService }: FtrProviderContext) { it('should handle unmute alert request appropriately', async () => { const { body: createdAlert } = await supertestWithoutAuth - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send(getTestAlertData({ enabled: false })) .expect(200); - objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'rule', 'alerting'); await alertUtils.muteAll(createdAlert.id); await alertUtils.unmuteAll(createdAlert.id); const { body: updatedAlert } = await supertestWithoutAuth - .get(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}`) + .get(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .expect(200); - expect(updatedAlert.muteAll).to.eql(false); + expect(updatedAlert.mute_all).to.eql(false); // Ensure AAD isn't broken await checkAAD({ @@ -51,5 +51,39 @@ export default function createUnmuteTests({ getService }: FtrProviderContext) { id: createdAlert.id, }); }); + + describe('legacy', () => { + it('should handle unmute alert request appropriately', async () => { + const { body: createdAlert } = await supertestWithoutAuth + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send(getTestAlertData({ enabled: false })) + .expect(200); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'rule', 'alerting'); + + await supertestWithoutAuth + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}/_mute_all`) + .set('kbn-xsrf', 'foo') + .expect(204); + await supertestWithoutAuth + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}/_unmute_all`) + .set('kbn-xsrf', 'foo') + .expect(204); + + const { body: updatedAlert } = await supertestWithoutAuth + .get(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule/${createdAlert.id}`) + .set('kbn-xsrf', 'foo') + .expect(200); + expect(updatedAlert.mute_all).to.eql(false); + + // Ensure AAD isn't broken + await checkAAD({ + supertest: supertestWithoutAuth, + spaceId: Spaces.space1.id, + type: 'alert', + id: createdAlert.id, + }); + }); + }); }); } diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/unmute_instance.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/unmute_instance.ts index 5cba2ab2305dec..e0c42136628d3d 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/unmute_instance.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/unmute_instance.ts @@ -28,20 +28,20 @@ export default function createUnmuteInstanceTests({ getService }: FtrProviderCon it('should handle unmute alert instance request appropriately', async () => { const { body: createdAlert } = await supertestWithoutAuth - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send(getTestAlertData({ enabled: false })) .expect(200); - objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'rule', 'alerting'); await alertUtils.muteInstance(createdAlert.id, '1'); await alertUtils.unmuteInstance(createdAlert.id, '1'); const { body: updatedAlert } = await supertestWithoutAuth - .get(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}`) + .get(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .expect(200); - expect(updatedAlert.mutedInstanceIds).to.eql([]); + expect(updatedAlert.muted_alert_ids).to.eql([]); // Ensure AAD isn't broken await checkAAD({ @@ -51,5 +51,47 @@ export default function createUnmuteInstanceTests({ getService }: FtrProviderCon id: createdAlert.id, }); }); + + describe('legacy', () => { + it('should handle unmute alert instance request appropriately', async () => { + const { body: createdAlert } = await supertestWithoutAuth + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send(getTestAlertData({ enabled: false })) + .expect(200); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'rule', 'alerting'); + + await supertestWithoutAuth + .post( + `${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${ + createdAlert.id + }/alert_instance/1/_mute` + ) + .set('kbn-xsrf', 'foo') + .expect(204); + await supertestWithoutAuth + .post( + `${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${ + createdAlert.id + }/alert_instance/1/_unmute` + ) + .set('kbn-xsrf', 'foo') + .expect(204); + + const { body: updatedAlert } = await supertestWithoutAuth + .get(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule/${createdAlert.id}`) + .set('kbn-xsrf', 'foo') + .expect(200); + expect(updatedAlert.muted_alert_ids).to.eql([]); + + // Ensure AAD isn't broken + await checkAAD({ + supertest: supertestWithoutAuth, + spaceId: Spaces.space1.id, + type: 'alert', + id: createdAlert.id, + }); + }); + }); }); } diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/update.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/update.ts index d97352966504ad..318da3e1140979 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/update.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/update.ts @@ -21,11 +21,11 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { it('should handle update alert request appropriately', async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'rule', 'alerting'); const updatedData = { name: 'bcd', @@ -36,9 +36,10 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { schedule: { interval: '12s' }, actions: [], throttle: '1m', + notify_when: 'onThrottleInterval', }; const response = await supertest - .put(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}`) + .put(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .send(updatedData) .expect(200); @@ -47,24 +48,24 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { ...updatedData, id: createdAlert.id, tags: ['bar'], - alertTypeId: 'test.noop', + rule_type_id: 'test.noop', consumer: 'alertsFixture', - createdBy: null, + created_by: null, enabled: true, - updatedBy: null, - apiKeyOwner: null, - muteAll: false, - mutedInstanceIds: [], - notifyWhen: 'onThrottleInterval', - scheduledTaskId: createdAlert.scheduledTaskId, - createdAt: response.body.createdAt, - updatedAt: response.body.updatedAt, - executionStatus: response.body.executionStatus, + updated_by: null, + api_key_owner: null, + mute_all: false, + muted_alert_ids: [], + notify_when: 'onThrottleInterval', + scheduled_task_id: createdAlert.scheduled_task_id, + created_at: response.body.created_at, + updated_at: response.body.updated_at, + execution_status: response.body.execution_status, }); - expect(Date.parse(response.body.createdAt)).to.be.greaterThan(0); - expect(Date.parse(response.body.updatedAt)).to.be.greaterThan(0); - expect(Date.parse(response.body.updatedAt)).to.be.greaterThan( - Date.parse(response.body.createdAt) + expect(Date.parse(response.body.created_at)).to.be.greaterThan(0); + expect(Date.parse(response.body.updated_at)).to.be.greaterThan(0); + expect(Date.parse(response.body.updated_at)).to.be.greaterThan( + Date.parse(response.body.created_at) ); // Ensure AAD isn't broken @@ -78,14 +79,14 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { it(`shouldn't update alert from another space`, async () => { const { body: createdAlert } = await supertest - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'rule', 'alerting'); await supertest - .put(`${getUrlPrefix(Spaces.other.id)}/api/alerts/alert/${createdAlert.id}`) + .put(`${getUrlPrefix(Spaces.other.id)}/api/alerting/rule/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .send({ name: 'bcd', @@ -96,6 +97,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { schedule: { interval: '12s' }, actions: [], throttle: '1m', + notify_when: 'onThrottleInterval', }) .expect(404, { statusCode: 404, @@ -103,5 +105,65 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { message: `Saved object [alert/${createdAlert.id}] not found`, }); }); + + describe('legacy', () => { + it('should handle update alert request appropriately', async () => { + const { body: createdAlert } = await supertest + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send(getTestAlertData()) + .expect(200); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'rule', 'alerting'); + + const updatedData = { + name: 'bcd', + tags: ['bar'], + params: { + foo: true, + }, + schedule: { interval: '12s' }, + actions: [], + throttle: '1m', + notifyWhen: 'onThrottleInterval', + }; + const response = await supertest + .put(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}`) + .set('kbn-xsrf', 'foo') + .send(updatedData) + .expect(200); + + expect(response.body).to.eql({ + ...updatedData, + id: createdAlert.id, + tags: ['bar'], + alertTypeId: 'test.noop', + consumer: 'alertsFixture', + createdBy: null, + enabled: true, + updatedBy: null, + apiKeyOwner: null, + muteAll: false, + mutedInstanceIds: [], + notifyWhen: 'onThrottleInterval', + scheduledTaskId: createdAlert.scheduled_task_id, + createdAt: response.body.createdAt, + updatedAt: response.body.updatedAt, + executionStatus: response.body.executionStatus, + }); + expect(Date.parse(response.body.createdAt)).to.be.greaterThan(0); + expect(Date.parse(response.body.updatedAt)).to.be.greaterThan(0); + expect(Date.parse(response.body.updatedAt)).to.be.greaterThan( + Date.parse(response.body.createdAt) + ); + + // Ensure AAD isn't broken + await checkAAD({ + supertest, + spaceId: Spaces.space1.id, + type: 'alert', + id: createdAlert.id, + }); + }); + }); }); } diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/update_api_key.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/update_api_key.ts index 32ce38d1417724..78ceadec44a9ae 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/update_api_key.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/update_api_key.ts @@ -32,19 +32,19 @@ export default function createUpdateApiKeyTests({ getService }: FtrProviderConte it('should handle update alert api key appropriately', async () => { const { body: createdAlert } = await supertestWithoutAuth - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'rule', 'alerting'); await alertUtils.updateApiKey(createdAlert.id); const { body: updatedAlert } = await supertestWithoutAuth - .get(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}`) + .get(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .expect(200); - expect(updatedAlert.apiKeyOwner).to.eql(null); + expect(updatedAlert.api_key_owner).to.eql(null); // Ensure AAD isn't broken await checkAAD({ @@ -57,11 +57,11 @@ export default function createUpdateApiKeyTests({ getService }: FtrProviderConte it(`shouldn't update alert api key from another space`, async () => { const { body: createdAlert } = await supertestWithoutAuth - .post(`${getUrlPrefix(Spaces.other.id)}/api/alerts/alert`) + .post(`${getUrlPrefix(Spaces.other.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); - objectRemover.add(Spaces.other.id, createdAlert.id, 'alert', 'alerts'); + objectRemover.add(Spaces.other.id, createdAlert.id, 'rule', 'alerting'); await alertUtils.getUpdateApiKeyRequest(createdAlert.id).expect(404, { statusCode: 404, @@ -69,5 +69,37 @@ export default function createUpdateApiKeyTests({ getService }: FtrProviderConte message: `Saved object [alert/${createdAlert.id}] not found`, }); }); + + describe('legacy', () => { + it('should handle update alert api key appropriately', async () => { + const { body: createdAlert } = await supertestWithoutAuth + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send(getTestAlertData()) + .expect(200); + objectRemover.add(Spaces.space1.id, createdAlert.id, 'rule', 'alerting'); + + await supertestWithoutAuth + .post( + `${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}/_update_api_key` + ) + .set('kbn-xsrf', 'foo') + .expect(204); + + const { body: updatedAlert } = await supertestWithoutAuth + .get(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule/${createdAlert.id}`) + .set('kbn-xsrf', 'foo') + .expect(200); + expect(updatedAlert.api_key_owner).to.eql(null); + + // Ensure AAD isn't broken + await checkAAD({ + supertest: supertestWithoutAuth, + spaceId: Spaces.space1.id, + type: 'alert', + id: createdAlert.id, + }); + }); + }); }); } diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts index d73488fab23730..661b452855a869 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts @@ -20,14 +20,19 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { async function getAlertsByName(name: string) { const { body: { data: alerts }, - } = await supertest.get(`/api/alerts/_find?search=${name}&search_fields=name`).expect(200); + } = await supertest + .get(`/api/alerting/rules/_find?search=${name}&search_fields=name`) + .expect(200); return alerts; } async function deleteAlerts(alertIds: string[]) { alertIds.forEach(async (alertId: string) => { - await supertest.delete(`/api/alerts/alert/${alertId}`).set('kbn-xsrf', 'foo').expect(204, ''); + await supertest + .delete(`/api/alerting/rule/${alertId}`) + .set('kbn-xsrf', 'foo') + .expect(204, ''); }); } diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts_list.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts_list.ts index e73e1fd8c37134..550e6ca455b225 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts_list.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts_list.ts @@ -20,7 +20,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { async function createAlertManualCleanup(overwrites: Record = {}) { const { body: createdAlert } = await supertest - .post(`/api/alerts/alert`) + .post(`/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send(getTestAlertData(overwrites)) .expect(200); @@ -29,7 +29,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { async function createFailingAlert() { return await createAlert({ - alertTypeId: 'test.failing', + rule_type_id: 'test.failing', schedule: { interval: '30s' }, }); } @@ -445,7 +445,6 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { actions: [ { id: action.id, - actionTypeId: '.slack', group: 'default', params: { level: 'info', message: 'gfghfhg' }, }, diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts index 5c4566121d4788..97f8b3f61dc892 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts @@ -42,7 +42,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { async function createAlert(overwrites: Record = {}) { const { body: createdAlert } = await supertest - .post(`/api/alerts/alert`) + .post(`/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send(getTestAlertData(overwrites)) .expect(200); @@ -52,11 +52,11 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { async function createAlwaysFiringAlert(overwrites: Record = {}) { const { body: createdAlert } = await supertest - .post(`/api/alerts/alert`) + .post(`/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send( getTestAlertData({ - alertTypeId: 'test.always-firing', + rule_type_id: 'test.always-firing', ...overwrites, }) ) @@ -93,14 +93,14 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { async function getAlertInstanceSummary(alertId: string) { const { body: summary } = await supertest - .get(`/api/alerts/alert/${alertId}/_instance_summary`) + .get(`/internal/alerting/rule/${alertId}/_alert_summary`) .expect(200); return summary; } async function muteAlertInstance(alertId: string, alertInstanceId: string) { const { body: response } = await supertest - .post(`/api/alerts/alert/${alertId}/alert_instance/${alertInstanceId}/_mute`) + .post(`/api/alerting/rule/${alertId}/alert/${alertInstanceId}/_mute`) .set('kbn-xsrf', 'foo') .expect(204); @@ -221,7 +221,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { before(async () => { await createAlwaysFiringAlert({ name: alertName, - alertTypeId: '.index-threshold', + rule_type_id: '.index-threshold', params: { aggType: 'count', termSize: 5, @@ -526,7 +526,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { // await first run to complete so we have an initial state await retry.try(async () => { - const { instances: alertInstances } = await getAlertInstanceSummary(alert.id); + const { alerts: alertInstances } = await getAlertInstanceSummary(alert.id); expect(Object.keys(alertInstances).length).to.eql(instances.length); }); }); @@ -552,12 +552,12 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const summary = await getAlertInstanceSummary(alert.id); const dateOnAllInstancesFromApiResponse: Record = mapValues( - summary.instances, + summary.alerts, (instance) => instance.activeStartDate ); const actionGroupNameOnAllInstancesFromApiResponse = mapValues( - summary.instances, + summary.alerts, (instance) => { const name = actionGroupNameFromId(instance.actionGroupId); return name ? ` (${name})` : ''; @@ -724,7 +724,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { // await first run to complete so we have an initial state await retry.try(async () => { - const { instances: alertInstances } = await getAlertInstanceSummary(alert.id); + const { alerts: alertInstances } = await getAlertInstanceSummary(alert.id); expect(Object.keys(alertInstances).length).to.eql(instances.length); }); @@ -749,7 +749,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { // Verify content await testSubjects.existOrFail('alertInstancesList'); - const { instances: alertInstances } = await getAlertInstanceSummary(alert.id); + const { alerts: alertInstances } = await getAlertInstanceSummary(alert.id); const items = await pageObjects.alertDetailsUI.getAlertInstancesList(); expect(items.length).to.eql(PAGE_SIZE); @@ -762,7 +762,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { // Verify content await testSubjects.existOrFail('alertInstancesList'); - const { instances: alertInstances } = await getAlertInstanceSummary(alert.id); + const { alerts: alertInstances } = await getAlertInstanceSummary(alert.id); await pageObjects.alertDetailsUI.clickPaginationNextPage(); diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/home_page.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/home_page.ts index 4aeadf5f1ae8a3..8ebb720930364b 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/home_page.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/home_page.ts @@ -91,7 +91,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { objectRemover.add(createdAction.id, 'action', 'actions'); const { body: createdAlert } = await supertest - .post(`/api/alerts/alert`) + .post(`/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send(getTestAlertData()) .expect(200); diff --git a/x-pack/test/functional_with_es_ssl/lib/get_test_data.ts b/x-pack/test/functional_with_es_ssl/lib/get_test_data.ts index 11ccd15571259a..8dc8a5e06645c5 100644 --- a/x-pack/test/functional_with_es_ssl/lib/get_test_data.ts +++ b/x-pack/test/functional_with_es_ssl/lib/get_test_data.ts @@ -16,11 +16,11 @@ export function getTestAlertData(overwrites = {}) { enabled: true, name: generateUniqueKey(), tags: ['foo', 'bar'], - alertTypeId: 'test.noop', + rule_type_id: 'test.noop', consumer: 'alerts', schedule: { interval: '1m' }, throttle: '1m', - notifyWhen: 'onThrottleInterval', + notify_when: 'onThrottleInterval', actions: [], params: {}, ...overwrites, From 01bf004193c89bb8327cfb37e7240ba8e7539fcd Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Tue, 30 Mar 2021 14:01:07 +0100 Subject: [PATCH 05/32] skip flaky suite (#95707) --- x-pack/test/accessibility/apps/security_solution.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/accessibility/apps/security_solution.ts b/x-pack/test/accessibility/apps/security_solution.ts index 0ee4e88d712c86..5fe1fec72525c4 100644 --- a/x-pack/test/accessibility/apps/security_solution.ts +++ b/x-pack/test/accessibility/apps/security_solution.ts @@ -14,7 +14,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const toasts = getService('toasts'); const testSubjects = getService('testSubjects'); - describe('Security Solution', () => { + // FLAKY: https://github.com/elastic/kibana/issues/95707 + describe.skip('Security Solution', () => { before(async () => { await security.testUser.setRoles(['superuser'], false); await common.navigateToApp('security'); From c6b37dec70ab16fd205f6bad573111012da1dd42 Mon Sep 17 00:00:00 2001 From: Scotty Bollinger Date: Tue, 30 Mar 2021 08:07:58 -0500 Subject: [PATCH 06/32] [Workplace Search] Add download diagnostics button to source settings (#95726) * Add routes * Add button to UI --- .../components/source_settings.test.tsx | 22 +++++++++ .../components/source_settings.tsx | 18 +++++++ .../views/content_sources/constants.ts | 22 +++++++++ .../routes/workplace_search/sources.test.ts | 48 +++++++++++++++++++ .../server/routes/workplace_search/sources.ts | 40 ++++++++++++++++ 5 files changed, 150 insertions(+) diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_settings.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_settings.test.tsx index f13189afe82526..c6cefba317cce9 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_settings.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_settings.test.tsx @@ -106,4 +106,26 @@ describe('SourceSettings', () => { sourceConfigData.configuredFields.publicKey ); }); + + describe('DownloadDiagnosticsButton', () => { + it('renders for org with correct href', () => { + const wrapper = shallow(); + + expect(wrapper.find('[data-test-subj="DownloadDiagnosticsButton"]').prop('href')).toEqual( + '/api/workplace_search/org/sources/123/download_diagnostics' + ); + }); + + it('renders for account with correct href', () => { + setMockValues({ + ...mockValues, + isOrganization: false, + }); + const wrapper = shallow(); + + expect(wrapper.find('[data-test-subj="DownloadDiagnosticsButton"]').prop('href')).toEqual( + '/api/workplace_search/account/sources/123/download_diagnostics' + ); + }); + }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_settings.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_settings.tsx index d99f9a4cb1a463..7ba53822534cfa 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_settings.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_settings.tsx @@ -44,6 +44,9 @@ import { SOURCE_CONFIG_LINK, SOURCE_REMOVE_TITLE, SOURCE_REMOVE_DESCRIPTION, + SYNC_DIAGNOSTICS_TITLE, + SYNC_DIAGNOSTICS_DESCRIPTION, + SYNC_DIAGNOSTICS_BUTTON, } from '../constants'; import { staticSourceData } from '../source_data'; import { SourceLogic } from '../source_logic'; @@ -82,6 +85,10 @@ export const SourceSettings: React.FC = () => { const { clientId, clientSecret, publicKey, consumerKey, baseUrl } = configuredFields || {}; + const diagnosticsPath = isOrganization + ? `/api/workplace_search/org/sources/${id}/download_diagnostics` + : `/api/workplace_search/account/sources/${id}/download_diagnostics`; + const handleNameChange = (e: ChangeEvent) => setValue(e.target.value); const submitNameChange = (e: FormEvent) => { @@ -167,6 +174,17 @@ export const SourceSettings: React.FC = () => { )} + + + {SYNC_DIAGNOSTICS_BUTTON} + + { }); }); + describe('GET /api/workplace_search/account/sources/{sourceId}/download_diagnostics', () => { + let mockRouter: MockRouter; + + beforeEach(() => { + jest.clearAllMocks(); + mockRouter = new MockRouter({ + method: 'get', + path: '/api/workplace_search/account/sources/{sourceId}/download_diagnostics', + }); + + registerAccountSourceDownloadDiagnosticsRoute({ + ...mockDependencies, + router: mockRouter.router, + }); + }); + + it('creates a request handler', () => { + expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ + path: '/ws/sources/:sourceId/download_diagnostics', + }); + }); + }); + describe('GET /api/workplace_search/org/sources', () => { let mockRouter: MockRouter; @@ -1061,6 +1086,29 @@ describe('sources routes', () => { }); }); + describe('GET /api/workplace_search/org/sources/{sourceId}/download_diagnostics', () => { + let mockRouter: MockRouter; + + beforeEach(() => { + jest.clearAllMocks(); + mockRouter = new MockRouter({ + method: 'get', + path: '/api/workplace_search/org/sources/{sourceId}/download_diagnostics', + }); + + registerOrgSourceDownloadDiagnosticsRoute({ + ...mockDependencies, + router: mockRouter.router, + }); + }); + + it('creates a request handler', () => { + expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ + path: '/ws/org/sources/:sourceId/download_diagnostics', + }); + }); + }); + describe('GET /api/workplace_search/org/settings/connectors', () => { let mockRouter: MockRouter; diff --git a/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.ts b/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.ts index d50ff6564b02e8..8257dd0dc52b09 100644 --- a/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.ts +++ b/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.ts @@ -401,6 +401,25 @@ export function registerAccountSourceReindexJobStatusRoute({ ); } +export function registerAccountSourceDownloadDiagnosticsRoute({ + router, + enterpriseSearchRequestHandler, +}: RouteDependencies) { + router.get( + { + path: '/api/workplace_search/account/sources/{sourceId}/download_diagnostics', + validate: { + params: schema.object({ + sourceId: schema.string(), + }), + }, + }, + enterpriseSearchRequestHandler.createRequest({ + path: '/ws/sources/:sourceId/download_diagnostics', + }) + ); +} + // Org routes export function registerOrgSourcesRoute({ router, @@ -750,6 +769,25 @@ export function registerOrgSourceReindexJobStatusRoute({ ); } +export function registerOrgSourceDownloadDiagnosticsRoute({ + router, + enterpriseSearchRequestHandler, +}: RouteDependencies) { + router.get( + { + path: '/api/workplace_search/org/sources/{sourceId}/download_diagnostics', + validate: { + params: schema.object({ + sourceId: schema.string(), + }), + }, + }, + enterpriseSearchRequestHandler.createRequest({ + path: '/ws/org/sources/:sourceId/download_diagnostics', + }) + ); +} + export function registerOrgSourceOauthConfigurationsRoute({ router, enterpriseSearchRequestHandler, @@ -900,6 +938,7 @@ export const registerSourcesRoutes = (dependencies: RouteDependencies) => { registerAccountSourceSchemasRoute(dependencies); registerAccountSourceReindexJobRoute(dependencies); registerAccountSourceReindexJobStatusRoute(dependencies); + registerAccountSourceDownloadDiagnosticsRoute(dependencies); registerOrgSourcesRoute(dependencies); registerOrgSourcesStatusRoute(dependencies); registerOrgSourceRoute(dependencies); @@ -915,6 +954,7 @@ export const registerSourcesRoutes = (dependencies: RouteDependencies) => { registerOrgSourceSchemasRoute(dependencies); registerOrgSourceReindexJobRoute(dependencies); registerOrgSourceReindexJobStatusRoute(dependencies); + registerOrgSourceDownloadDiagnosticsRoute(dependencies); registerOrgSourceOauthConfigurationsRoute(dependencies); registerOrgSourceOauthConfigurationRoute(dependencies); registerOauthConnectorParamsRoute(dependencies); From 9d8a2f183ef0d83e20d48c54556b32065662da8f Mon Sep 17 00:00:00 2001 From: Shahzad Date: Tue, 30 Mar 2021 15:19:35 +0200 Subject: [PATCH 07/32] [Lens] Expose events from lens for lens embeddable (#94670) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../embedded_lens_example/public/app.tsx | 32 ++- .../embeddable/embeddable.test.tsx | 201 ++++++++++++++++++ .../embeddable/embeddable.tsx | 141 +++++++----- 3 files changed, 317 insertions(+), 57 deletions(-) diff --git a/x-pack/examples/embedded_lens_example/public/app.tsx b/x-pack/examples/embedded_lens_example/public/app.tsx index 71af1b824eb80e..d2af98e8e85e4b 100644 --- a/x-pack/examples/embedded_lens_example/public/app.tsx +++ b/x-pack/examples/embedded_lens_example/public/app.tsx @@ -111,7 +111,13 @@ export const App = (props: { defaultIndexPattern: IndexPattern | null; }) => { const [color, setColor] = useState('green'); + const [isLoading, setIsLoading] = useState(false); const LensComponent = props.plugins.lens.EmbeddableComponent; + + const [time, setTime] = useState({ + from: 'now-5d', + to: 'now', + }); return ( @@ -138,6 +144,7 @@ export const App = (props: { { // eslint-disable-next-line no-bitwise const newColor = '#' + ((Math.random() * 0xffffff) << 0).toString(16); @@ -153,10 +160,7 @@ export const App = (props: { onClick={() => { props.plugins.lens.navigateToPrefilledEditor({ id: '', - timeRange: { - from: 'now-5d', - to: 'now', - }, + timeRange: time, attributes: getLensAttributes(props.defaultIndexPattern!, color), }); // eslint-disable-next-line no-bitwise @@ -171,11 +175,23 @@ export const App = (props: { { + setIsLoading(val); + }} + onBrushEnd={({ range }) => { + setTime({ + from: new Date(range[0]).toISOString(), + to: new Date(range[1]).toISOString(), + }); + }} + onFilter={(_data) => { + // call back event for on filter event + }} + onTableRowClick={(_data) => { + // call back event for on table row click event + }} /> ) : ( diff --git a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.test.tsx index 925358c434e5d1..efe16c30935bea 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.test.tsx @@ -679,4 +679,205 @@ describe('embeddable', () => { expect(expressionRenderer).toHaveBeenCalledTimes(1); }); + + it('should call onload after rerender and onData$ call ', async () => { + const onLoad = jest.fn(); + + expressionRenderer = jest.fn(({ onData$ }) => { + setTimeout(() => { + onData$?.({}); + }, 10); + + return null; + }); + + const embeddable = new Embeddable( + { + timefilter: dataPluginMock.createSetupContract().query.timefilter.timefilter, + attributeService, + expressionRenderer, + basePath, + indexPatternService: {} as IndexPatternsContract, + editable: true, + getTrigger, + documentToExpression: () => + Promise.resolve({ + ast: { + type: 'expression', + chain: [ + { type: 'function', function: 'my', arguments: {} }, + { type: 'function', function: 'expression', arguments: {} }, + ], + }, + errors: undefined, + }), + }, + ({ id: '123', onLoad } as unknown) as LensEmbeddableInput + ); + + await embeddable.initializeSavedVis({ id: '123' } as LensEmbeddableInput); + embeddable.render(mountpoint); + + expect(onLoad).toHaveBeenCalledWith(true); + expect(onLoad).toHaveBeenCalledTimes(1); + + await new Promise((resolve) => setTimeout(resolve, 20)); + + // loading should become false + expect(onLoad).toHaveBeenCalledTimes(2); + expect(onLoad).toHaveBeenNthCalledWith(2, false); + + expect(expressionRenderer).toHaveBeenCalledTimes(1); + + embeddable.updateInput({ + searchSessionId: 'newSession', + }); + embeddable.reload(); + + // loading should become again true + expect(onLoad).toHaveBeenCalledTimes(3); + expect(onLoad).toHaveBeenNthCalledWith(3, true); + + await new Promise((resolve) => setTimeout(resolve, 0)); + + expect(expressionRenderer).toHaveBeenCalledTimes(2); + + await new Promise((resolve) => setTimeout(resolve, 20)); + + // loading should again become false + expect(onLoad).toHaveBeenCalledTimes(4); + expect(onLoad).toHaveBeenNthCalledWith(4, false); + }); + + it('should call onFilter event on filter call ', async () => { + const onFilter = jest.fn(); + + expressionRenderer = jest.fn(({ onEvent }) => { + setTimeout(() => { + onEvent?.({ name: 'filter', data: { pings: false } }); + }, 10); + + return null; + }); + + const embeddable = new Embeddable( + { + timefilter: dataPluginMock.createSetupContract().query.timefilter.timefilter, + attributeService, + expressionRenderer, + basePath, + indexPatternService: {} as IndexPatternsContract, + editable: true, + getTrigger, + documentToExpression: () => + Promise.resolve({ + ast: { + type: 'expression', + chain: [ + { type: 'function', function: 'my', arguments: {} }, + { type: 'function', function: 'expression', arguments: {} }, + ], + }, + errors: undefined, + }), + }, + ({ id: '123', onFilter } as unknown) as LensEmbeddableInput + ); + + await embeddable.initializeSavedVis({ id: '123' } as LensEmbeddableInput); + embeddable.render(mountpoint); + + await new Promise((resolve) => setTimeout(resolve, 20)); + + expect(onFilter).toHaveBeenCalledWith({ pings: false }); + expect(onFilter).toHaveBeenCalledTimes(1); + }); + + it('should call onBrush event on brushing', async () => { + const onBrushEnd = jest.fn(); + + expressionRenderer = jest.fn(({ onEvent }) => { + setTimeout(() => { + onEvent?.({ name: 'brush', data: { range: [0, 1] } }); + }, 10); + + return null; + }); + + const embeddable = new Embeddable( + { + timefilter: dataPluginMock.createSetupContract().query.timefilter.timefilter, + attributeService, + expressionRenderer, + basePath, + indexPatternService: {} as IndexPatternsContract, + editable: true, + getTrigger, + documentToExpression: () => + Promise.resolve({ + ast: { + type: 'expression', + chain: [ + { type: 'function', function: 'my', arguments: {} }, + { type: 'function', function: 'expression', arguments: {} }, + ], + }, + errors: undefined, + }), + }, + ({ id: '123', onBrushEnd } as unknown) as LensEmbeddableInput + ); + + await embeddable.initializeSavedVis({ id: '123' } as LensEmbeddableInput); + embeddable.render(mountpoint); + + await new Promise((resolve) => setTimeout(resolve, 20)); + + expect(onBrushEnd).toHaveBeenCalledWith({ range: [0, 1] }); + expect(onBrushEnd).toHaveBeenCalledTimes(1); + }); + + it('should call onTableRowClick event ', async () => { + const onTableRowClick = jest.fn(); + + expressionRenderer = jest.fn(({ onEvent }) => { + setTimeout(() => { + onEvent?.({ name: 'tableRowContextMenuClick', data: { name: 'test' } }); + }, 10); + + return null; + }); + + const embeddable = new Embeddable( + { + timefilter: dataPluginMock.createSetupContract().query.timefilter.timefilter, + attributeService, + expressionRenderer, + basePath, + indexPatternService: {} as IndexPatternsContract, + editable: true, + getTrigger, + documentToExpression: () => + Promise.resolve({ + ast: { + type: 'expression', + chain: [ + { type: 'function', function: 'my', arguments: {} }, + { type: 'function', function: 'expression', arguments: {} }, + ], + }, + errors: undefined, + }), + }, + ({ id: '123', onTableRowClick } as unknown) as LensEmbeddableInput + ); + + await embeddable.initializeSavedVis({ id: '123' } as LensEmbeddableInput); + embeddable.render(mountpoint); + + await new Promise((resolve) => setTimeout(resolve, 20)); + + expect(onTableRowClick).toHaveBeenCalledWith({ name: 'test' }); + expect(onTableRowClick).toHaveBeenCalledTimes(1); + }); }); diff --git a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx index bfeb645d7bd5ff..b395352b614774 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx @@ -45,6 +45,9 @@ import { isLensBrushEvent, isLensFilterEvent, isLensTableRowContextMenuClickEvent, + LensBrushEvent, + LensFilterEvent, + LensTableRowContextMenuEvent, } from '../../types'; import { IndexPatternsContract } from '../../../../../../src/plugins/data/public'; @@ -63,6 +66,10 @@ interface LensBaseEmbeddableInput extends EmbeddableInput { renderMode?: RenderMode; style?: React.CSSProperties; className?: string; + onBrushEnd?: (data: LensBrushEvent['data']) => void; + onLoad?: (isLoading: boolean) => void; + onFilter?: (data: LensFilterEvent['data']) => void; + onTableRowClick?: (data: LensTableRowContextMenuEvent['data']) => void; } export type LensByValueInput = { @@ -103,6 +110,8 @@ export class Embeddable private isInitialized = false; private activeData: Partial | undefined; private errors: ErrorMessage[] | undefined; + private inputReloadSubscriptions: Subscription[]; + private isDestroyed?: boolean; private externalSearchContext: { timeRange?: TimeRange; @@ -133,65 +142,76 @@ export class Embeddable const input$ = this.getInput$(); + this.inputReloadSubscriptions = []; + // Lens embeddable does not re-render when embeddable input changes in // general, to improve performance. This line makes sure the Lens embeddable // re-renders when anything in ".dynamicActions" (e.g. drilldowns) changes. - input$ - .pipe( - map((input) => input.enhancements?.dynamicActions), - distinctUntilChanged((a, b) => isEqual(a, b)), - skip(1) - ) - .subscribe((input) => { - this.reload(); - }); + this.inputReloadSubscriptions.push( + input$ + .pipe( + map((input) => input.enhancements?.dynamicActions), + distinctUntilChanged((a, b) => isEqual(a, b)), + skip(1) + ) + .subscribe((input) => { + this.reload(); + }) + ); // Lens embeddable does not re-render when embeddable input changes in // general, to improve performance. This line makes sure the Lens embeddable // re-renders when dashboard view mode switches between "view/edit". This is // needed to see the changes to ".dynamicActions" (e.g. drilldowns) when // dashboard's mode is toggled. - input$ - .pipe( - map((input) => input.viewMode), - distinctUntilChanged(), - skip(1) - ) - .subscribe((input) => { - this.reload(); - }); + this.inputReloadSubscriptions.push( + input$ + .pipe( + map((input) => input.viewMode), + distinctUntilChanged(), + skip(1) + ) + .subscribe((input) => { + this.reload(); + }) + ); // Re-initialize the visualization if either the attributes or the saved object id changes - input$ - .pipe( - distinctUntilChanged((a, b) => - isEqual( - ['attributes' in a && a.attributes, 'savedObjectId' in a && a.savedObjectId], - ['attributes' in b && b.attributes, 'savedObjectId' in b && b.savedObjectId] - ) - ), - skip(1) - ) - .subscribe(async (input) => { - await this.initializeSavedVis(input); - this.reload(); - }); + + this.inputReloadSubscriptions.push( + input$ + .pipe( + distinctUntilChanged((a, b) => + isEqual( + ['attributes' in a && a.attributes, 'savedObjectId' in a && a.savedObjectId], + ['attributes' in b && b.attributes, 'savedObjectId' in b && b.savedObjectId] + ) + ), + skip(1) + ) + .subscribe(async (input) => { + await this.initializeSavedVis(input); + this.reload(); + }) + ); // Update search context and reload on changes related to search - this.getUpdated$() - .pipe(map(() => this.getInput())) - .pipe( - distinctUntilChanged((a, b) => - isEqual( - [a.filters, a.query, a.timeRange, a.searchSessionId], - [b.filters, b.query, b.timeRange, b.searchSessionId] - ) - ), - skip(1) - ) - .subscribe(async (input) => { - this.onContainerStateChanged(input); - }); + this.inputReloadSubscriptions.push( + this.getUpdated$() + .pipe(map(() => this.getInput())) + .pipe( + distinctUntilChanged((a, b) => + isEqual( + [a.filters, a.query, a.timeRange, a.searchSessionId], + [b.filters, b.query, b.timeRange, b.searchSessionId] + ) + ), + skip(1) + ) + .subscribe(async (input) => { + this.onContainerStateChanged(input); + }) + ); } public supportedTriggers() { @@ -222,7 +242,7 @@ export class Embeddable this.onFatalError(e); return false; }); - if (!attributes) { + if (!attributes || this.isDestroyed) { return; } this.savedVis = { @@ -268,6 +288,10 @@ export class Embeddable inspectorAdapters?: Partial | undefined ) => { this.activeData = inspectorAdapters; + if (this.input.onLoad) { + // once onData$ is get's called from expression renderer, loading becomes false + this.input.onLoad(false); + } }; /** @@ -277,9 +301,12 @@ export class Embeddable */ render(domNode: HTMLElement | Element) { this.domNode = domNode; - if (!this.savedVis || !this.isInitialized) { + if (!this.savedVis || !this.isInitialized || this.isDestroyed) { return; } + if (this.input.onLoad) { + this.input.onLoad(true); + } const input = this.getInput(); render( 0) { + this.inputReloadSubscriptions.forEach((reloadSub) => { + reloadSub.unsubscribe(); + }); + } if (this.domNode) { unmountComponentAtNode(this.domNode); } From 81b46931f803c2cccf3cb271707d4c88de1f690e Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Tue, 30 Mar 2021 07:48:07 -0600 Subject: [PATCH 08/32] [file_upload] add has_import_permission route (#95190) * [file_upload] add has_import_permission route * remove ml hasImportPermissions * fix tsconfig path * tslint * review feedback * make pipeline check optional since geojson upload does not use pipeline * ts cleanup * make geojson permission failure message actionable * revert privilege change in functional test * add global_index_pattern_management_all permission to functional test * rename hasPipeline to checkHasManagePipeline * add api integration test * tslint * revert change to es_search_source * simpilify error message when users can't create index pattern Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- x-pack/plugins/file_upload/common/types.ts | 4 + x-pack/plugins/file_upload/kibana.json | 1 + .../plugins/file_upload/public/api/index.ts | 22 +++ .../public/lazy_load_bundle/index.ts | 4 + x-pack/plugins/file_upload/public/plugin.ts | 8 +- x-pack/plugins/file_upload/server/index.ts | 4 +- x-pack/plugins/file_upload/server/plugin.ts | 13 +- x-pack/plugins/file_upload/server/routes.ts | 55 ++++++- x-pack/plugins/file_upload/server/types.ts | 12 ++ x-pack/plugins/file_upload/tsconfig.json | 3 +- .../layers/file_upload_wizard/config.tsx | 12 ++ .../layers/file_upload_wizard/wizard.tsx | 4 +- x-pack/plugins/maps/public/kibana_services.ts | 4 +- .../file_datavisualizer_view.js | 13 +- .../components/import_view/import_view.js | 9 +- .../file_based/components/utils/index.ts | 8 +- .../file_based/components/utils/utils.ts | 25 --- .../security/server/authorization/index.ts | 1 + x-pack/plugins/security/server/index.ts | 1 + .../apis/file_upload/has_import_permission.ts | 146 ++++++++++++++++++ .../api_integration/apis/file_upload/index.ts | 14 ++ x-pack/test/api_integration/apis/index.ts | 1 + .../import_geojson/add_layer_import_panel.js | 6 +- 23 files changed, 314 insertions(+), 56 deletions(-) create mode 100644 x-pack/plugins/file_upload/server/types.ts create mode 100644 x-pack/test/api_integration/apis/file_upload/has_import_permission.ts create mode 100644 x-pack/test/api_integration/apis/file_upload/index.ts diff --git a/x-pack/plugins/file_upload/common/types.ts b/x-pack/plugins/file_upload/common/types.ts index 3c385bdf8f3df1..0fc59e2b525a8f 100644 --- a/x-pack/plugins/file_upload/common/types.ts +++ b/x-pack/plugins/file_upload/common/types.ts @@ -7,6 +7,10 @@ import { ES_FIELD_TYPES } from '../../../../src/plugins/data/common'; +export interface HasImportPermission { + hasImportPermission: boolean; +} + export interface InputOverrides { [key: string]: string | undefined; } diff --git a/x-pack/plugins/file_upload/kibana.json b/x-pack/plugins/file_upload/kibana.json index 6d55e4f46d7e87..a1c585e5343335 100644 --- a/x-pack/plugins/file_upload/kibana.json +++ b/x-pack/plugins/file_upload/kibana.json @@ -5,5 +5,6 @@ "server": true, "ui": true, "requiredPlugins": ["data", "usageCollection"], + "optionalPlugins": ["security"], "requiredBundles": ["kibanaReact"] } diff --git a/x-pack/plugins/file_upload/public/api/index.ts b/x-pack/plugins/file_upload/public/api/index.ts index 8884c398fa2e61..281537cbbde167 100644 --- a/x-pack/plugins/file_upload/public/api/index.ts +++ b/x-pack/plugins/file_upload/public/api/index.ts @@ -8,12 +8,14 @@ import React from 'react'; import { FileUploadComponentProps, lazyLoadFileUploadModules } from '../lazy_load_bundle'; import type { IImporter, ImportFactoryOptions } from '../importer'; +import { HasImportPermission } from '../../common'; export interface FileUploadStartApi { getFileUploadComponent(): Promise>; importerFactory(format: string, options: ImportFactoryOptions): Promise; getMaxBytes(): number; getMaxBytesFormatted(): string; + hasImportPermission(params: HasImportPermissionParams): Promise; } export async function getFileUploadComponent(): Promise< @@ -30,3 +32,23 @@ export async function importerFactory( const fileUploadModules = await lazyLoadFileUploadModules(); return fileUploadModules.importerFactory(format, options); } + +interface HasImportPermissionParams { + checkCreateIndexPattern: boolean; + checkHasManagePipeline: boolean; + indexName?: string; +} + +export async function hasImportPermission(params: HasImportPermissionParams): Promise { + const fileUploadModules = await lazyLoadFileUploadModules(); + try { + const resp = await fileUploadModules.getHttp().fetch({ + path: `/internal/file_upload/has_import_permission`, + method: 'GET', + query: { ...params }, + }); + return resp.hasImportPermission; + } catch (error) { + return false; + } +} diff --git a/x-pack/plugins/file_upload/public/lazy_load_bundle/index.ts b/x-pack/plugins/file_upload/public/lazy_load_bundle/index.ts index a61faa5bef0c4f..807d2fae52bf84 100644 --- a/x-pack/plugins/file_upload/public/lazy_load_bundle/index.ts +++ b/x-pack/plugins/file_upload/public/lazy_load_bundle/index.ts @@ -8,7 +8,9 @@ import React from 'react'; import { FeatureCollection } from 'geojson'; import { IndexPattern } from 'src/plugins/data/public'; +import { HttpStart } from 'src/core/public'; import { IImporter, ImportFactoryOptions, ImportResults } from '../importer'; +import { getHttp } from '../kibana_services'; export interface FileUploadComponentProps { isIndexingTriggered: boolean; @@ -27,6 +29,7 @@ let loadModulesPromise: Promise; interface LazyLoadedFileUploadModules { JsonUploadAndParse: React.ComponentType; importerFactory: (format: string, options: ImportFactoryOptions) => IImporter | undefined; + getHttp: () => HttpStart; } export async function lazyLoadFileUploadModules(): Promise { @@ -40,6 +43,7 @@ export async function lazyLoadFileUploadModules(): Promise new FileUploadPlugin(); +export const plugin = (initializerContext: PluginInitializerContext) => + new FileUploadPlugin(initializerContext); diff --git a/x-pack/plugins/file_upload/server/plugin.ts b/x-pack/plugins/file_upload/server/plugin.ts index 94b4f0368d562c..5a4b59fe4f5e6d 100644 --- a/x-pack/plugins/file_upload/server/plugin.ts +++ b/x-pack/plugins/file_upload/server/plugin.ts @@ -6,20 +6,27 @@ */ import { i18n } from '@kbn/i18n'; -import { CoreSetup, CoreStart, Plugin } from 'src/core/server'; +import { CoreSetup, CoreStart, Logger, Plugin, PluginInitializerContext } from 'src/core/server'; import { schema } from '@kbn/config-schema'; import { fileUploadRoutes } from './routes'; import { initFileUploadTelemetry } from './telemetry'; import { UsageCollectionSetup } from '../../../../src/plugins/usage_collection/server'; import { UI_SETTING_MAX_FILE_SIZE, MAX_FILE_SIZE } from '../common'; +import { StartDeps } from './types'; interface SetupDeps { usageCollection: UsageCollectionSetup; } export class FileUploadPlugin implements Plugin { - async setup(coreSetup: CoreSetup, plugins: SetupDeps) { - fileUploadRoutes(coreSetup.http.createRouter()); + private readonly _logger: Logger; + + constructor(initializerContext: PluginInitializerContext) { + this._logger = initializerContext.logger.get(); + } + + async setup(coreSetup: CoreSetup, plugins: SetupDeps) { + fileUploadRoutes(coreSetup, this._logger); coreSetup.uiSettings.register({ [UI_SETTING_MAX_FILE_SIZE]: { diff --git a/x-pack/plugins/file_upload/server/routes.ts b/x-pack/plugins/file_upload/server/routes.ts index 4f4adb29f6b0bb..6d7eb77f390690 100644 --- a/x-pack/plugins/file_upload/server/routes.ts +++ b/x-pack/plugins/file_upload/server/routes.ts @@ -6,7 +6,8 @@ */ import { schema } from '@kbn/config-schema'; -import { IRouter, IScopedClusterClient } from 'kibana/server'; +import { IScopedClusterClient } from 'kibana/server'; +import { CoreSetup, Logger } from 'src/core/server'; import { MAX_FILE_SIZE_BYTES, IngestPipelineWrapper, @@ -20,6 +21,8 @@ import { importDataProvider } from './import_data'; import { updateTelemetry } from './telemetry'; import { analyzeFileQuerySchema, importFileBodySchema, importFileQuerySchema } from './schemas'; +import { CheckPrivilegesPayload } from '../../security/server'; +import { StartDeps } from './types'; function importData( client: IScopedClusterClient, @@ -37,7 +40,55 @@ function importData( /** * Routes for the file upload. */ -export function fileUploadRoutes(router: IRouter) { +export function fileUploadRoutes(coreSetup: CoreSetup, logger: Logger) { + const router = coreSetup.http.createRouter(); + + router.get( + { + path: '/internal/file_upload/has_import_permission', + validate: { + query: schema.object({ + indexName: schema.maybe(schema.string()), + checkCreateIndexPattern: schema.boolean(), + checkHasManagePipeline: schema.boolean(), + }), + }, + }, + async (context, request, response) => { + try { + const [, pluginsStart] = await coreSetup.getStartServices(); + const { indexName, checkCreateIndexPattern, checkHasManagePipeline } = request.query; + + const authorizationService = pluginsStart.security?.authz; + const requiresAuthz = authorizationService?.mode.useRbacForRequest(request) ?? false; + + if (!authorizationService || !requiresAuthz) { + return response.ok({ body: { hasImportPermission: true } }); + } + + const checkPrivilegesPayload: CheckPrivilegesPayload = { + elasticsearch: { + cluster: checkHasManagePipeline ? ['manage_pipeline'] : [], + index: indexName ? { [indexName]: ['create', 'create_index'] } : {}, + }, + }; + if (checkCreateIndexPattern) { + checkPrivilegesPayload.kibana = [ + authorizationService.actions.savedObject.get('index-pattern', 'create'), + ]; + } + + const checkPrivileges = authorizationService.checkPrivilegesDynamicallyWithRequest(request); + const checkPrivilegesResp = await checkPrivileges(checkPrivilegesPayload); + + return response.ok({ body: { hasImportPermission: checkPrivilegesResp.hasAllRequested } }); + } catch (e) { + logger.warn(`Unable to check import permission, error: ${e.message}`); + return response.ok({ body: { hasImportPermission: false } }); + } + } + ); + /** * @apiGroup FileDataVisualizer * diff --git a/x-pack/plugins/file_upload/server/types.ts b/x-pack/plugins/file_upload/server/types.ts new file mode 100644 index 00000000000000..d23661ebae711f --- /dev/null +++ b/x-pack/plugins/file_upload/server/types.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { SecurityPluginStart } from '../..//security/server'; + +export interface StartDeps { + security?: SecurityPluginStart; +} diff --git a/x-pack/plugins/file_upload/tsconfig.json b/x-pack/plugins/file_upload/tsconfig.json index bebb08e6dd5e33..887a05af31174b 100644 --- a/x-pack/plugins/file_upload/tsconfig.json +++ b/x-pack/plugins/file_upload/tsconfig.json @@ -11,6 +11,7 @@ "references": [ { "path": "../../../src/core/tsconfig.json" }, { "path": "../../../src/plugins/data/tsconfig.json" }, - { "path": "../../../src/plugins/usage_collection/tsconfig.json" } + { "path": "../../../src/plugins/usage_collection/tsconfig.json" }, + { "path": "../security/tsconfig.json" }, ] } diff --git a/x-pack/plugins/maps/public/classes/layers/file_upload_wizard/config.tsx b/x-pack/plugins/maps/public/classes/layers/file_upload_wizard/config.tsx index 4b3f8967be3bbb..49f35c491ccf05 100644 --- a/x-pack/plugins/maps/public/classes/layers/file_upload_wizard/config.tsx +++ b/x-pack/plugins/maps/public/classes/layers/file_upload_wizard/config.tsx @@ -9,12 +9,24 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; import { LayerWizard, RenderWizardArguments } from '../../layers/layer_wizard_registry'; import { ClientFileCreateSourceEditor, INDEX_SETUP_STEP_ID, INDEXING_STEP_ID } from './wizard'; +import { getFileUpload } from '../../../kibana_services'; export const uploadLayerWizardConfig: LayerWizard = { categories: [], description: i18n.translate('xpack.maps.fileUploadWizard.description', { defaultMessage: 'Index GeoJSON data in Elasticsearch', }), + disabledReason: i18n.translate('xpack.maps.fileUploadWizard.disabledDesc', { + defaultMessage: + 'Unable to upload files, you are missing the Kibana privilege "Index Pattern Management".', + }), + getIsDisabled: async () => { + const hasImportPermission = await getFileUpload().hasImportPermission({ + checkCreateIndexPattern: true, + checkHasManagePipeline: false, + }); + return !hasImportPermission; + }, icon: 'importAction', prerequisiteSteps: [ { diff --git a/x-pack/plugins/maps/public/classes/layers/file_upload_wizard/wizard.tsx b/x-pack/plugins/maps/public/classes/layers/file_upload_wizard/wizard.tsx index 30e3317a3a3cf3..a6ff14d20f238c 100644 --- a/x-pack/plugins/maps/public/classes/layers/file_upload_wizard/wizard.tsx +++ b/x-pack/plugins/maps/public/classes/layers/file_upload_wizard/wizard.tsx @@ -15,7 +15,7 @@ import { DEFAULT_MAX_RESULT_WINDOW, SCALING_TYPES, } from '../../../../common/constants'; -import { getFileUploadComponent } from '../../../kibana_services'; +import { getFileUpload } from '../../../kibana_services'; import { GeoJsonFileSource } from '../../sources/geojson_file_source'; import { VectorLayer } from '../../layers/vector_layer'; import { createDefaultLayerDescriptor } from '../../sources/es_search_source'; @@ -65,7 +65,7 @@ export class ClientFileCreateSourceEditor extends Component pluginsStart.data.indexPatterns; export const getAutocompleteService = () => pluginsStart.data.autocomplete; export const getInspector = () => pluginsStart.inspector; -export const getFileUploadComponent = async () => { - return await pluginsStart.fileUpload.getFileUploadComponent(); -}; +export const getFileUpload = () => pluginsStart.fileUpload; export const getUiSettings = () => coreStart.uiSettings; export const getIsDarkMode = () => getUiSettings().get('theme:darkMode', false); export const getIndexPatternSelectComponent = () => pluginsStart.data.ui.IndexPatternSelect; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_datavisualizer_view/file_datavisualizer_view.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_datavisualizer_view/file_datavisualizer_view.js index 90c2b2bf186393..b184913e51b14e 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_datavisualizer_view/file_datavisualizer_view.js +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_datavisualizer_view/file_datavisualizer_view.js @@ -20,13 +20,7 @@ import { FileCouldNotBeRead, FileTooLarge } from './file_error_callouts'; import { EditFlyout } from '../edit_flyout'; import { ExplanationFlyout } from '../explanation_flyout'; import { ImportView } from '../import_view'; -import { - DEFAULT_LINES_TO_SAMPLE, - readFile, - createUrlOverrides, - processResults, - hasImportPermission, -} from '../utils'; +import { DEFAULT_LINES_TO_SAMPLE, readFile, createUrlOverrides, processResults } from '../utils'; import { getFileUpload } from '../../../../util/dependency_cache'; import { MODE } from './constants'; @@ -67,7 +61,10 @@ export class FileDataVisualizerView extends Component { // check the user has the correct permission to import data. // note, calling hasImportPermission with no arguments just checks the // cluster privileges, the user will still need index privileges to create and ingest - const hasPermissionToImport = await hasImportPermission(); + const hasPermissionToImport = await getFileUpload().hasImportPermission({ + checkCreateIndexPattern: false, + checkHasManagePipeline: true, + }); this.setState({ hasPermissionToImport }); } diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/import_view.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/import_view.js index 28f4f2e2ba9e44..04175f46c92017 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/import_view.js +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/import_view.js @@ -35,7 +35,6 @@ import { import { ExperimentalBadge } from '../experimental_badge'; import { getIndexPatternNames, loadIndexPatterns } from '../../../../util/index_utils'; import { ml } from '../../../../services/ml_api_service'; -import { hasImportPermission } from '../utils'; const DEFAULT_TIME_FIELD = '@timestamp'; const DEFAULT_INDEX_SETTINGS = { number_of_shards: 1 }; @@ -124,7 +123,13 @@ export class ImportView extends Component { }, async () => { // check to see if the user has permission to create and ingest data into the specified index - if ((await hasImportPermission(index)) === false) { + if ( + (await getFileUpload().hasImportPermission({ + checkCreateIndexPattern: createIndexPattern, + checkHasManagePipeline: true, + indexName: index, + })) === false + ) { errors.push( i18n.translate('xpack.ml.fileDatavisualizer.importView.importPermissionError', { defaultMessage: diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/utils/index.ts b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/utils/index.ts index 209f2ef5ad174f..cbefc12833d2d1 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/utils/index.ts +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/utils/index.ts @@ -5,10 +5,4 @@ * 2.0. */ -export { - createUrlOverrides, - hasImportPermission, - processResults, - readFile, - DEFAULT_LINES_TO_SAMPLE, -} from './utils'; +export { createUrlOverrides, processResults, readFile, DEFAULT_LINES_TO_SAMPLE } from './utils'; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/utils/utils.ts b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/utils/utils.ts index 85ca27cdf90fff..49e5da565b9273 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/utils/utils.ts +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/utils/utils.ts @@ -6,7 +6,6 @@ */ import { isEqual } from 'lodash'; -import { ml } from '../../../../services/ml_api_service'; import { AnalysisResult, InputOverrides } from '../../../../../../../file_upload/common'; import { MB } from '../../../../../../../file_upload/public'; @@ -136,27 +135,3 @@ export function processResults({ results, overrides }: AnalysisResult) { linesToSample, }; } - -/** - * A check for the minimum privileges needed to create and ingest data into an index. - * If called with no indexName, the check will just look for the minimum cluster privileges. - * @param {string} indexName - * @returns {Promise} - */ -export async function hasImportPermission(indexName: string) { - const priv: { cluster: string[]; index?: any } = { - cluster: ['cluster:admin/ingest/pipeline/put'], - }; - - if (indexName !== undefined) { - priv.index = [ - { - names: [indexName], - privileges: ['indices:data/write/bulk', 'indices:data/write/index', 'indices:admin/create'], - }, - ]; - } - - const resp = await ml.hasPrivileges(priv); - return resp.securityDisabled === true || resp.has_all_requested === true; -} diff --git a/x-pack/plugins/security/server/authorization/index.ts b/x-pack/plugins/security/server/authorization/index.ts index 1eca1de2e68146..6cbb4d10c75e48 100644 --- a/x-pack/plugins/security/server/authorization/index.ts +++ b/x-pack/plugins/security/server/authorization/index.ts @@ -9,3 +9,4 @@ export { Actions } from './actions'; export { AuthorizationService, AuthorizationServiceSetup } from './authorization_service'; export { CheckSavedObjectsPrivileges } from './check_saved_objects_privileges'; export { featurePrivilegeIterator } from './privileges'; +export { CheckPrivilegesPayload } from './types'; diff --git a/x-pack/plugins/security/server/index.ts b/x-pack/plugins/security/server/index.ts index 7120f480bbc42e..6412562af8a415 100644 --- a/x-pack/plugins/security/server/index.ts +++ b/x-pack/plugins/security/server/index.ts @@ -26,6 +26,7 @@ export type { InvalidateAPIKeyResult, GrantAPIKeyResult, } from './authentication'; +export type { CheckPrivilegesPayload } from './authorization'; export { LegacyAuditLogger, AuditLogger, diff --git a/x-pack/test/api_integration/apis/file_upload/has_import_permission.ts b/x-pack/test/api_integration/apis/file_upload/has_import_permission.ts new file mode 100644 index 00000000000000..3977c4f0a06ad9 --- /dev/null +++ b/x-pack/test/api_integration/apis/file_upload/has_import_permission.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default ({ getService }: FtrProviderContext) => { + const supertestWithoutAuth = getService('supertestWithoutAuth'); + const security = getService('security'); + + const IMPORTER_ROLE_NAME = 'importer'; + const IMPORTER_USER_NAME = 'importer'; + const IMPORT_USER_PASSWORD = `${IMPORTER_USER_NAME}-password`; + const INDEX_NAME = 'myNewIndex'; + + describe('GET /internal/file_upload/has_import_permission', () => { + it('should return true when user has all permissions', async () => { + try { + await security.role.create(IMPORTER_ROLE_NAME, { + elasticsearch: { + cluster: ['manage_pipeline'], + indices: [ + { + names: [INDEX_NAME], + privileges: ['create', 'create_index'], + }, + ], + }, + kibana: [ + { + feature: { + indexPatterns: ['all'], + }, + spaces: ['*'], + }, + ], + }); + + await security.user.create(IMPORTER_USER_NAME, { + password: IMPORT_USER_PASSWORD, + roles: [IMPORTER_ROLE_NAME], + }); + + const resp = await supertestWithoutAuth + .get( + `/internal/file_upload/has_import_permission\ +?checkCreateIndexPattern=true\ +&checkHasManagePipeline=true\ +&indexName=${INDEX_NAME}` + ) + .auth(IMPORTER_USER_NAME, IMPORT_USER_PASSWORD) + .set('kbn-xsrf', 'kibana') + .expect(200); + + expect(resp.body.hasImportPermission).to.be(true); + } finally { + await security.role.delete(IMPORTER_ROLE_NAME); + await security.user.delete(IMPORTER_USER_NAME); + } + }); + + it('should return false when user can not create index pattern when checkCreateIndexPattern=true', async () => { + try { + await security.role.create(IMPORTER_ROLE_NAME, {}); + + await security.user.create(IMPORTER_USER_NAME, { + password: IMPORT_USER_PASSWORD, + roles: [IMPORTER_ROLE_NAME], + }); + + const resp = await supertestWithoutAuth + .get( + `/internal/file_upload/has_import_permission\ +?checkCreateIndexPattern=true\ +&checkHasManagePipeline=false` + ) + .auth(IMPORTER_USER_NAME, IMPORT_USER_PASSWORD) + .set('kbn-xsrf', 'kibana') + .send() + .expect(200); + + expect(resp.body.hasImportPermission).to.be(false); + } finally { + await security.role.delete(IMPORTER_ROLE_NAME); + await security.user.delete(IMPORTER_USER_NAME); + } + }); + + it('should return false when user can not create pipeline when checkHasManagePipeline=true', async () => { + try { + await security.role.create(IMPORTER_ROLE_NAME, {}); + + await security.user.create(IMPORTER_USER_NAME, { + password: IMPORT_USER_PASSWORD, + roles: [IMPORTER_ROLE_NAME], + }); + + const resp = await supertestWithoutAuth + .get( + `/internal/file_upload/has_import_permission\ +?checkCreateIndexPattern=false\ +&checkHasManagePipeline=true` + ) + .auth(IMPORTER_USER_NAME, IMPORT_USER_PASSWORD) + .set('kbn-xsrf', 'kibana') + .expect(200); + + expect(resp.body.hasImportPermission).to.be(false); + } finally { + await security.role.delete(IMPORTER_ROLE_NAME); + await security.user.delete(IMPORTER_USER_NAME); + } + }); + + it('should return false when user does not have index permissions', async () => { + try { + await security.role.create(IMPORTER_ROLE_NAME, {}); + + await security.user.create(IMPORTER_USER_NAME, { + password: IMPORT_USER_PASSWORD, + roles: [IMPORTER_ROLE_NAME], + }); + + const resp = await supertestWithoutAuth + .get( + `/internal/file_upload/has_import_permission\ +?checkCreateIndexPattern=false\ +&checkHasManagePipeline=false\ +&indexName=${INDEX_NAME}` + ) + .auth(IMPORTER_USER_NAME, IMPORT_USER_PASSWORD) + .set('kbn-xsrf', 'kibana') + .expect(200); + + expect(resp.body.hasImportPermission).to.be(false); + } finally { + await security.role.delete(IMPORTER_ROLE_NAME); + await security.user.delete(IMPORTER_USER_NAME); + } + }); + }); +}; diff --git a/x-pack/test/api_integration/apis/file_upload/index.ts b/x-pack/test/api_integration/apis/file_upload/index.ts new file mode 100644 index 00000000000000..cf0159997fa112 --- /dev/null +++ b/x-pack/test/api_integration/apis/file_upload/index.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { + describe('File upload', function () { + loadTestFile(require.resolve('./has_import_permission')); + }); +} diff --git a/x-pack/test/api_integration/apis/index.ts b/x-pack/test/api_integration/apis/index.ts index 26c3a2e251b1ec..e0328444701777 100644 --- a/x-pack/test/api_integration/apis/index.ts +++ b/x-pack/test/api_integration/apis/index.ts @@ -36,5 +36,6 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./upgrade_assistant')); loadTestFile(require.resolve('./searchprofiler')); loadTestFile(require.resolve('./painless_lab')); + loadTestFile(require.resolve('./file_upload')); }); } diff --git a/x-pack/test/functional/apps/maps/import_geojson/add_layer_import_panel.js b/x-pack/test/functional/apps/maps/import_geojson/add_layer_import_panel.js index b40f9a4bc233e4..5af0a2c6d1edb5 100644 --- a/x-pack/test/functional/apps/maps/import_geojson/add_layer_import_panel.js +++ b/x-pack/test/functional/apps/maps/import_geojson/add_layer_import_panel.js @@ -17,7 +17,11 @@ export default function ({ getPageObjects, getService }) { describe('GeoJSON import layer panel', () => { before(async () => { - await security.testUser.setRoles(['global_maps_all', 'geoall_data_writer']); + await security.testUser.setRoles([ + 'global_maps_all', + 'geoall_data_writer', + 'global_index_pattern_management_all', + ]); await PageObjects.maps.openNewMap(); }); From 36e567b858c19c394dd8c36dea05b475e95f2499 Mon Sep 17 00:00:00 2001 From: Jonathan Budzenski Date: Tue, 30 Mar 2021 09:06:27 -0500 Subject: [PATCH 09/32] [optimizer] Compress sass assets in production (#94846) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../__snapshots__/basic_optimization.test.ts.snap | 2 +- packages/kbn-optimizer/src/worker/webpack.config.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/kbn-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap b/packages/kbn-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap index ffc8d7ea8c5052..c175979f0e820e 100644 --- a/packages/kbn-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap +++ b/packages/kbn-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap @@ -101,7 +101,7 @@ OptimizerConfig { } `; -exports[`prepares assets for distribution: bar bundle 1`] = `"!function(modules){var installedModules={};function __webpack_require__(moduleId){if(installedModules[moduleId])return installedModules[moduleId].exports;var module=installedModules[moduleId]={i:moduleId,l:!1,exports:{}};return modules[moduleId].call(module.exports,module,module.exports,__webpack_require__),module.l=!0,module.exports}__webpack_require__.m=modules,__webpack_require__.c=installedModules,__webpack_require__.d=function(exports,name,getter){__webpack_require__.o(exports,name)||Object.defineProperty(exports,name,{enumerable:!0,get:getter})},__webpack_require__.r=function(exports){\\"undefined\\"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(exports,Symbol.toStringTag,{value:\\"Module\\"}),Object.defineProperty(exports,\\"__esModule\\",{value:!0})},__webpack_require__.t=function(value,mode){if(1&mode&&(value=__webpack_require__(value)),8&mode)return value;if(4&mode&&\\"object\\"==typeof value&&value&&value.__esModule)return value;var ns=Object.create(null);if(__webpack_require__.r(ns),Object.defineProperty(ns,\\"default\\",{enumerable:!0,value:value}),2&mode&&\\"string\\"!=typeof value)for(var key in value)__webpack_require__.d(ns,key,function(key){return value[key]}.bind(null,key));return ns},__webpack_require__.n=function(module){var getter=module&&module.__esModule?function(){return module.default}:function(){return module};return __webpack_require__.d(getter,\\"a\\",getter),getter},__webpack_require__.o=function(object,property){return Object.prototype.hasOwnProperty.call(object,property)},__webpack_require__.p=\\"\\",__webpack_require__(__webpack_require__.s=3)}([function(module,exports,__webpack_require__){\\"use strict\\";var memo,isOldIE=function(){return void 0===memo&&(memo=Boolean(window&&document&&document.all&&!window.atob)),memo},getTarget=function(){var memo={};return function(target){if(void 0===memo[target]){var styleTarget=document.querySelector(target);if(window.HTMLIFrameElement&&styleTarget instanceof window.HTMLIFrameElement)try{styleTarget=styleTarget.contentDocument.head}catch(e){styleTarget=null}memo[target]=styleTarget}return memo[target]}}(),stylesInDom=[];function getIndexByIdentifier(identifier){for(var result=-1,i=0;i Date: Tue, 30 Mar 2021 16:09:34 +0200 Subject: [PATCH 10/32] [Security Solution] User can filter Trusted Applications by Hash, Path, Signer, or Trusted App name (#95532) * Allows filter param. Empty by default * Uses KQL for filter from Ui * Adds search bar to dispatch trusted apps search. Fixes some type errors. Added filter into the list View state * Fix tests and added a new one. Also split query on array to improve readability * Decouple query parser to be used outside the middleware * Reuse code using a map * Filter by term using wildcards. Updates test * Adds useCallback to memoize function --- .../public/management/common/routing.test.ts | 1 + .../public/management/common/routing.ts | 6 ++ .../state/trusted_apps_list_page_state.ts | 2 + .../pages/trusted_apps/store/builders.ts | 1 + .../pages/trusted_apps/store/middleware.ts | 6 ++ .../pages/trusted_apps/store/reducer.test.ts | 13 +++- .../trusted_apps/store/selectors.test.ts | 3 + .../pages/trusted_apps/store/selectors.ts | 8 ++- .../pages/trusted_apps/store/utils.test.ts | 26 ++++++++ .../pages/trusted_apps/store/utils.ts | 18 ++++++ .../pages/trusted_apps/test_utils/index.ts | 1 + .../view/components/search_bar/index.test.tsx | 60 +++++++++++++++++++ .../view/components/search_bar/index.tsx | 55 +++++++++++++++++ .../view/trusted_apps_page.test.tsx | 31 ++++++++++ .../trusted_apps/view/trusted_apps_page.tsx | 4 ++ 15 files changed, 230 insertions(+), 5 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/utils.test.ts create mode 100644 x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/utils.ts create mode 100644 x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/search_bar/index.test.tsx create mode 100644 x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/search_bar/index.tsx diff --git a/x-pack/plugins/security_solution/public/management/common/routing.test.ts b/x-pack/plugins/security_solution/public/management/common/routing.test.ts index 1d06a78c2aa69e..a1662c21012bee 100644 --- a/x-pack/plugins/security_solution/public/management/common/routing.test.ts +++ b/x-pack/plugins/security_solution/public/management/common/routing.test.ts @@ -127,6 +127,7 @@ describe('routing', () => { page_size: 20, show: 'create', view_type: 'list', + filter: '', }; expect(getTrustedAppsListPath(location)).toEqual( diff --git a/x-pack/plugins/security_solution/public/management/common/routing.ts b/x-pack/plugins/security_solution/public/management/common/routing.ts index bf754720f314be..82aa96714d70eb 100644 --- a/x-pack/plugins/security_solution/public/management/common/routing.ts +++ b/x-pack/plugins/security_solution/public/management/common/routing.ts @@ -109,6 +109,7 @@ const normalizeTrustedAppsPageLocation = ( ...(!isDefaultOrMissing(location.view_type, 'grid') ? { view_type: location.view_type } : {}), ...(!isDefaultOrMissing(location.show, undefined) ? { show: location.show } : {}), ...(!isDefaultOrMissing(location.id, undefined) ? { id: location.id } : {}), + ...(!isDefaultOrMissing(location.filter, '') ? { filter: location.filter } : ''), }; } else { return {}; @@ -141,9 +142,14 @@ const extractPageSize = (query: querystring.ParsedUrlQuery): number => { return MANAGEMENT_PAGE_SIZE_OPTIONS.includes(pageSize) ? pageSize : MANAGEMENT_DEFAULT_PAGE_SIZE; }; +const extractFilter = (query: querystring.ParsedUrlQuery): string => { + return extractFirstParamValue(query, 'filter') || ''; +}; + export const extractListPaginationParams = (query: querystring.ParsedUrlQuery) => ({ page_index: extractPageIndex(query), page_size: extractPageSize(query), + filter: extractFilter(query), }); export const extractTrustedAppsListPageLocation = ( diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/trusted_apps_list_page_state.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/trusted_apps_list_page_state.ts index 1c1fca4b55abc2..e37b0f262603d8 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/trusted_apps_list_page_state.ts +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/trusted_apps_list_page_state.ts @@ -22,6 +22,7 @@ export interface TrustedAppsListData { pageSize: number; timestamp: number; totalItemsCount: number; + filter: string; } export type ViewType = 'list' | 'grid'; @@ -33,6 +34,7 @@ export interface TrustedAppsListPageLocation { show?: 'create' | 'edit'; /** Used for editing. The ID of the selected trusted app */ id?: string; + filter: string; } export interface TrustedAppsListPageState { diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/builders.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/builders.ts index ece2c9e29750f3..5a22badec9afbc 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/builders.ts +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/builders.ts @@ -56,6 +56,7 @@ export const initialTrustedAppsPageState = (): TrustedAppsListPageState => ({ show: undefined, id: undefined, view_type: 'grid', + filter: '', }, active: false, }); diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/middleware.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/middleware.ts index 7f940f14f9c6c0..71a49caf66fd6a 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/middleware.ts +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/middleware.ts @@ -46,6 +46,7 @@ import { getLastLoadedListResourceState, getCurrentLocationPageIndex, getCurrentLocationPageSize, + getCurrentLocationFilter, needsRefreshOfListData, getCreationSubmissionResourceState, getCreationDialogFormEntry, @@ -63,6 +64,7 @@ import { getListItems, editItemState, } from './selectors'; +import { parseQueryFilterToKQL } from './utils'; import { toUpdateTrustedApp } from '../../../../../common/endpoint/service/trusted_apps/to_update_trusted_app'; const createTrustedAppsListResourceStateChangedAction = ( @@ -90,9 +92,12 @@ const refreshListIfNeeded = async ( try { const pageIndex = getCurrentLocationPageIndex(store.getState()); const pageSize = getCurrentLocationPageSize(store.getState()); + const filter = getCurrentLocationFilter(store.getState()); + const response = await trustedAppsService.getTrustedAppsList({ page: pageIndex + 1, per_page: pageSize, + kuery: parseQueryFilterToKQL(filter) || undefined, }); store.dispatch( @@ -104,6 +109,7 @@ const refreshListIfNeeded = async ( pageSize, totalItemsCount: response.total, timestamp: Date.now(), + filter, }, }) ); diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/reducer.test.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/reducer.test.ts index 6965172ef773d5..58193eea3de52b 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/reducer.test.ts +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/reducer.test.ts @@ -31,7 +31,7 @@ describe('reducer', () => { initialState, createUserChangedUrlAction( '/trusted_apps', - '?page_index=5&page_size=50&show=create&view_type=list' + '?page_index=5&page_size=50&show=create&view_type=list&filter=test' ) ); @@ -43,6 +43,7 @@ describe('reducer', () => { show: 'create', view_type: 'list', id: undefined, + filter: 'test', }, active: true, }); @@ -50,7 +51,10 @@ describe('reducer', () => { it('extracts default pagination parameters when invalid provided', () => { const result = trustedAppsPageReducer( - { ...initialState, location: { page_index: 5, page_size: 50, view_type: 'grid' } }, + { + ...initialState, + location: { page_index: 5, page_size: 50, view_type: 'grid', filter: '' }, + }, createUserChangedUrlAction('/trusted_apps', '?page_index=b&page_size=60&show=a&view_type=c') ); @@ -59,7 +63,10 @@ describe('reducer', () => { it('extracts default pagination parameters when none provided', () => { const result = trustedAppsPageReducer( - { ...initialState, location: { page_index: 5, page_size: 50, view_type: 'grid' } }, + { + ...initialState, + location: { page_index: 5, page_size: 50, view_type: 'grid', filter: '' }, + }, createUserChangedUrlAction('/trusted_apps') ); diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/selectors.test.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/selectors.test.ts index 3616f79489b4ab..4d8ce097a72633 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/selectors.test.ts +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/selectors.test.ts @@ -93,6 +93,7 @@ describe('selectors', () => { page_index: 0, page_size: 10, view_type: 'grid', + filter: '', }; expect(needsRefreshOfListData({ ...initialState, listView, active: true, location })).toBe( @@ -174,6 +175,7 @@ describe('selectors', () => { page_index: 3, page_size: 10, view_type: 'grid', + filter: '', }; expect(getCurrentLocationPageIndex({ ...initialState, location })).toBe(3); @@ -186,6 +188,7 @@ describe('selectors', () => { page_index: 0, page_size: 20, view_type: 'grid', + filter: '', }; expect(getCurrentLocationPageSize({ ...initialState, location })).toBe(20); diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/selectors.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/selectors.ts index 7c131c3eaa7a99..43506f98193a03 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/selectors.ts +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/selectors.ts @@ -30,14 +30,14 @@ export const needsRefreshOfListData = (state: Immutable { return ( data.pageIndex === location.page_index && data.pageSize === location.page_size && - data.timestamp >= freshDataTimestamp + data.timestamp >= freshDataTimestamp && + data.filter === location.filter ); }) ); @@ -69,6 +69,10 @@ export const getCurrentLocationPageSize = (state: Immutable): string => { + return state.location.filter; +}; + export const getListTotalItemsCount = (state: Immutable): number => { return getLastLoadedResourceState(state.listView.listResourceState)?.data.totalItemsCount || 0; }; diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/utils.test.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/utils.test.ts new file mode 100644 index 00000000000000..14a5b3e809a537 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/utils.test.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { parseQueryFilterToKQL } from './utils'; + +describe('utils', () => { + describe('parseQueryFilterToKQL', () => { + it('should parse simple query without term', () => { + expect(parseQueryFilterToKQL('')).toBe(''); + }); + it('should parse simple query with term', () => { + expect(parseQueryFilterToKQL('simpleQuery')).toBe( + 'exception-list-agnostic.attributes.name:*simpleQuery* OR exception-list-agnostic.attributes.description:*simpleQuery* OR exception-list-agnostic.attributes.entries.value:*simpleQuery* OR exception-list-agnostic.attributes.entries.entries.value:*simpleQuery*' + ); + }); + it('should parse complex query with term', () => { + expect(parseQueryFilterToKQL('complex query')).toBe( + 'exception-list-agnostic.attributes.name:*complex* *query* OR exception-list-agnostic.attributes.description:*complex* *query* OR exception-list-agnostic.attributes.entries.value:*complex* *query* OR exception-list-agnostic.attributes.entries.entries.value:*complex* *query*' + ); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/utils.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/utils.ts new file mode 100644 index 00000000000000..a2f19a60030561 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/utils.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const parseQueryFilterToKQL = (filter: string): string => { + if (!filter) return ''; + const kuery = [`name`, `description`, `entries.value`, `entries.entries.value`] + .map( + (field) => + `exception-list-agnostic.attributes.${field}:*${filter.trim().replace(/\s/gm, '* *')}*` + ) + .join(' OR '); + + return kuery; +}; diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/test_utils/index.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/test_utils/index.ts index faffc6b04a0cd4..7783ac96e192d4 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/test_utils/index.ts +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/test_utils/index.ts @@ -79,6 +79,7 @@ export const createTrustedAppsListData = ( pageIndex: fullPagination.pageIndex, totalItemsCount: fullPagination.totalItemCount, timestamp, + filter: '', }; }; diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/search_bar/index.test.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/search_bar/index.test.tsx new file mode 100644 index 00000000000000..f12c979c2afe4a --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/search_bar/index.test.tsx @@ -0,0 +1,60 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { mount } from 'enzyme'; +import React from 'react'; + +import { SearchBar } from '.'; + +let onSearchMock: jest.Mock; + +interface EuiFieldSearchPropsFake { + onSearch(value: string): void; +} + +describe('Search bar', () => { + beforeEach(() => { + onSearchMock = jest.fn(); + }); + + const getElement = (defaultValue: string = '') => ( + + ); + + it('should have a default value', () => { + const expectedDefaultValue = 'this is a default value'; + const element = mount(getElement(expectedDefaultValue)); + const defaultValue = element.find('[data-test-subj="trustedAppSearchField"]').first().props() + .defaultValue; + expect(defaultValue).toBe(expectedDefaultValue); + }); + + it('should dispatch search action when submit search field', () => { + const expectedDefaultValue = 'this is a default value'; + const element = mount(getElement()); + expect(onSearchMock).toHaveBeenCalledTimes(0); + const searchFieldProps = element + .find('[data-test-subj="trustedAppSearchField"]') + .first() + .props() as EuiFieldSearchPropsFake; + + searchFieldProps.onSearch(expectedDefaultValue); + + expect(onSearchMock).toHaveBeenCalledTimes(1); + expect(onSearchMock).toHaveBeenCalledWith(expectedDefaultValue); + }); + + it('should dispatch search action when click on button', () => { + const expectedDefaultValue = 'this is a default value'; + const element = mount(getElement(expectedDefaultValue)); + expect(onSearchMock).toHaveBeenCalledTimes(0); + + element.find('[data-test-subj="trustedAppSearchButton"]').first().simulate('click'); + expect(onSearchMock).toHaveBeenCalledTimes(1); + expect(onSearchMock).toHaveBeenCalledWith(expectedDefaultValue); + }); +}); diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/search_bar/index.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/search_bar/index.tsx new file mode 100644 index 00000000000000..6fc6b2af4d585e --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/search_bar/index.tsx @@ -0,0 +1,55 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { memo, useCallback, useState } from 'react'; +import { EuiFlexGroup, EuiFlexItem, EuiFieldSearch, EuiButton } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +export interface SearchBarProps { + defaultValue?: string; + onSearch(value: string): void; +} + +export const SearchBar = memo(({ defaultValue = '', onSearch }) => { + const [query, setQuery] = useState(defaultValue); + + const handleOnChangeSearchField = useCallback( + (ev: React.ChangeEvent) => setQuery(ev.target.value), + [setQuery] + ); + const handleOnSearch = useCallback(() => onSearch(query), [query, onSearch]); + + return ( + + + + + + + {i18n.translate('xpack.securitySolution.trustedapps.list.search.button', { + defaultMessage: 'Refresh', + })} + + + + ); +}); + +SearchBar.displayName = 'SearchBar'; diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.test.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.test.tsx index 6093a8d66e0d8e..8a1b1ccfa51735 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.test.tsx @@ -860,4 +860,35 @@ describe('When on the Trusted Apps Page', () => { expect(await renderResult.findByTestId('trustedAppEmptyState')).not.toBeNull(); }); }); + + describe('and the search is dispatched', () => { + const renderWithListData = async () => { + const result = render(); + await act(async () => { + await waitForAction('trustedAppsListResourceStateChanged'); + }); + return result; + }; + + beforeEach(() => mockListApis(coreStart.http)); + + it('search bar is filled with query params', async () => { + reactTestingLibrary.act(() => { + history.push('/trusted_apps?filter=test'); + }); + const result = await renderWithListData(); + expect(result.getByDisplayValue('test')).not.toBeNull(); + }); + + it('search action is dispatched', async () => { + reactTestingLibrary.act(() => { + history.push('/trusted_apps?filter=test'); + }); + const result = await renderWithListData(); + await act(async () => { + fireEvent.click(result.getByTestId('trustedAppSearchButton')); + await waitForAction('userChangedUrl'); + }); + }); + }); }); diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.tsx index feba6eca048563..1bf44769c15b4a 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.tsx @@ -39,6 +39,7 @@ import { TrustedAppsListPageRouteState } from '../../../../../common/endpoint/ty import { useNavigateToAppEventHandler } from '../../../../common/hooks/endpoint/use_navigate_to_app_event_handler'; import { ABOUT_TRUSTED_APPS } from './translations'; import { EmptyState } from './components/empty_state'; +import { SearchBar } from './components/search_bar'; export const TrustedAppsPage = memo(() => { const { state: routeState } = useLocation(); @@ -57,6 +58,7 @@ export const TrustedAppsPage = memo(() => { const handleViewTypeChange = useTrustedAppsNavigateCallback((viewType: ViewType) => ({ view_type: viewType, })); + const handleOnSearch = useTrustedAppsNavigateCallback((query: string) => ({ filter: query })); const showCreateFlyout = !!location.show; @@ -94,12 +96,14 @@ export const TrustedAppsPage = memo(() => { /> )} + {doEntriesExist ? ( + Date: Tue, 30 Mar 2021 17:27:33 +0300 Subject: [PATCH 11/32] Update vega related modules (master) (#92651) * Update vega and vega-lite * Update specs for vega-lite Co-authored-by: Wylie Conlon Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Uladzislau Lasitsa --- package.json | 4 +- .../vis_type_vega/public/default.spec.hjson | 2 +- .../public/test_utils/default.spec.json | 4 +- .../public/test_utils/vegalite_graph.json | 2 +- .../get_usage_collector.test.ts | 2 +- .../scatterplot_matrix_vega_lite_spec.test.ts | 2 +- .../scatterplot_matrix_vega_lite_spec.ts | 2 +- .../components/vega_chart/vega_chart_view.tsx | 4 +- .../get_roc_curve_chart_vega_lite_spec.tsx | 2 +- yarn.lock | 69 ++++++++----------- 10 files changed, 39 insertions(+), 54 deletions(-) diff --git a/package.json b/package.json index 2654c433ac5fa8..e379123269847f 100644 --- a/package.json +++ b/package.json @@ -409,10 +409,10 @@ "utility-types": "^3.10.0", "uuid": "3.3.2", "vega": "^5.19.1", - "vega-lite": "^4.17.0", + "vega-lite": "^5.0.0", "vega-schema-url-parser": "^2.1.0", "vega-spec-injector": "^0.0.2", - "vega-tooltip": "^0.25.0", + "vega-tooltip": "^0.25.1", "venn.js": "0.2.20", "vinyl": "^2.2.0", "vt-pbf": "^3.1.1", diff --git a/src/plugins/vis_type_vega/public/default.spec.hjson b/src/plugins/vis_type_vega/public/default.spec.hjson index ace1950f4e9097..834bfdc4ff2780 100644 --- a/src/plugins/vis_type_vega/public/default.spec.hjson +++ b/src/plugins/vis_type_vega/public/default.spec.hjson @@ -6,7 +6,7 @@ Welcome to Vega visualizations. Here you can design your own dataviz from scrat This example graph shows the document count in all indexes in the current time range. You might need to adjust the time filter in the upper right corner. */ - $schema: https://vega.github.io/schema/vega-lite/v4.json + $schema: https://vega.github.io/schema/vega-lite/v5.json title: Event counts from all indexes // Define the data source diff --git a/src/plugins/vis_type_vega/public/test_utils/default.spec.json b/src/plugins/vis_type_vega/public/test_utils/default.spec.json index 8cf763647115f2..c1db62fa8035af 100644 --- a/src/plugins/vis_type_vega/public/test_utils/default.spec.json +++ b/src/plugins/vis_type_vega/public/test_utils/default.spec.json @@ -1,5 +1,5 @@ { - "$schema": "https://vega.github.io/schema/vega-lite/v4.json", + "$schema": "https://vega.github.io/schema/vega-lite/v5.json", "title": "Event counts from all indexes", "data": { "url": { @@ -30,7 +30,7 @@ "x": { "field": "key", "type": "temporal", - "axis": { "title": false } + "axis": { "title": null } }, "y": { "field": "doc_count", diff --git a/src/plugins/vis_type_vega/public/test_utils/vegalite_graph.json b/src/plugins/vis_type_vega/public/test_utils/vegalite_graph.json index 5394f009b074fd..5a5e72f59022b7 100644 --- a/src/plugins/vis_type_vega/public/test_utils/vegalite_graph.json +++ b/src/plugins/vis_type_vega/public/test_utils/vegalite_graph.json @@ -1,5 +1,5 @@ { - "$schema": "https://vega.github.io/schema/vega-lite/v4.json", + "$schema": "https://vega.github.io/schema/vega-lite/v5.json", "data": { "format": {"property": "aggregations.time_buckets.buckets"}, "values": { diff --git a/src/plugins/vis_type_vega/server/usage_collector/get_usage_collector.test.ts b/src/plugins/vis_type_vega/server/usage_collector/get_usage_collector.test.ts index a83409f936078f..ce815cba4a4e28 100644 --- a/src/plugins/vis_type_vega/server/usage_collector/get_usage_collector.test.ts +++ b/src/plugins/vis_type_vega/server/usage_collector/get_usage_collector.test.ts @@ -20,7 +20,7 @@ const mockedSavedObjects = [ visState: JSON.stringify({ type: 'vega', params: { - spec: '{"$schema": "https://vega.github.io/schema/vega-lite/v4.json" }', + spec: '{"$schema": "https://vega.github.io/schema/vega-lite/v5.json" }', }, }), }, diff --git a/x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix_vega_lite_spec.test.ts b/x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix_vega_lite_spec.test.ts index 1ce03119c161d7..12a4d9257c5e72 100644 --- a/x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix_vega_lite_spec.test.ts +++ b/x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix_vega_lite_spec.test.ts @@ -6,7 +6,7 @@ */ // @ts-ignore -import { compile } from 'vega-lite/build-es5/vega-lite'; +import { compile } from 'vega-lite/build/vega-lite'; import euiThemeLight from '@elastic/eui/dist/eui_theme_light.json'; diff --git a/x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix_vega_lite_spec.ts b/x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix_vega_lite_spec.ts index 9d8e3b6546327e..f10ccb6e92a901 100644 --- a/x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix_vega_lite_spec.ts +++ b/x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix_vega_lite_spec.ts @@ -7,7 +7,7 @@ // There is still an issue with Vega Lite's typings with the strict mode Kibana is using. // @ts-ignore -import type { TopLevelSpec } from 'vega-lite/build-es5/vega-lite'; +import type { TopLevelSpec } from 'vega-lite/build/vega-lite'; import euiThemeLight from '@elastic/eui/dist/eui_theme_light.json'; diff --git a/x-pack/plugins/ml/public/application/components/vega_chart/vega_chart_view.tsx b/x-pack/plugins/ml/public/application/components/vega_chart/vega_chart_view.tsx index 7774def574b696..5064cf64352273 100644 --- a/x-pack/plugins/ml/public/application/components/vega_chart/vega_chart_view.tsx +++ b/x-pack/plugins/ml/public/application/components/vega_chart/vega_chart_view.tsx @@ -9,11 +9,11 @@ import React, { useMemo, useEffect, FC } from 'react'; // There is still an issue with Vega Lite's typings with the strict mode Kibana is using. // @ts-ignore -import type { TopLevelSpec } from 'vega-lite/build-es5/vega-lite'; +import type { TopLevelSpec } from 'vega-lite/build/vega-lite'; // There is still an issue with Vega Lite's typings with the strict mode Kibana is using. // @ts-ignore -import { compile } from 'vega-lite/build-es5/vega-lite'; +import { compile } from 'vega-lite/build/vega-lite'; import { parse, View, Warn } from 'vega'; import { Handler } from 'vega-tooltip'; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/get_roc_curve_chart_vega_lite_spec.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/get_roc_curve_chart_vega_lite_spec.tsx index e482c89a96dc0c..e9a6925476b025 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/get_roc_curve_chart_vega_lite_spec.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/get_roc_curve_chart_vega_lite_spec.tsx @@ -7,7 +7,7 @@ // There is still an issue with Vega Lite's typings with the strict mode Kibana is using. // @ts-ignore -import type { TopLevelSpec } from 'vega-lite/build-es5/vega-lite'; +import type { TopLevelSpec } from 'vega-lite/build/vega-lite'; import { euiPaletteColorBlind, euiPaletteGray } from '@elastic/eui'; diff --git a/yarn.lock b/yarn.lock index 0bbfe98f5d1d86..486752dce55878 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9626,7 +9626,7 @@ cliui@^6.0.0: strip-ansi "^6.0.0" wrap-ansi "^6.2.0" -cliui@^7.0.0, cliui@^7.0.2: +cliui@^7.0.2: version "7.0.2" resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.2.tgz#e3a412e1d5ec0ccbe50d1b4120fc8164e97881f4" integrity sha512-lhpKkuUj67j5JgZIPZxLe7nSa4MQoojzRVWQyzMqBp2hBg6gwRjUDAwC1YDeBaC3APDBKNnjWbv2mlDF4XgOSA== @@ -18238,10 +18238,10 @@ json-stringify-pretty-compact@1.2.0: resolved "https://registry.yarnpkg.com/json-stringify-pretty-compact/-/json-stringify-pretty-compact-1.2.0.tgz#0bc316b5e6831c07041fc35612487fb4e9ab98b8" integrity sha512-/11Pj1OyX814QMKO7K8l85SHPTr/KsFxHp8GE2zVa0BtJgGimDjXHfM3FhC7keQdWDea7+nXf+f1de7ATZcZkQ== -json-stringify-pretty-compact@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/json-stringify-pretty-compact/-/json-stringify-pretty-compact-2.0.0.tgz#e77c419f52ff00c45a31f07f4c820c2433143885" - integrity sha512-WRitRfs6BGq4q8gTgOy4ek7iPFXjbra0H3PmDLKm2xnZ+Gh1HUhiKGgCZkSPNULlP7mvfu6FV/mOLhCarspADQ== +json-stringify-pretty-compact@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/json-stringify-pretty-compact/-/json-stringify-pretty-compact-3.0.0.tgz#f71ef9d82ef16483a407869556588e91b681d9ab" + integrity sha512-Rc2suX5meI0S3bfdZuA7JMFBGkJ875ApfVyq2WHELjBiiG22My/l7/8zPpH/CfFVQHuVLd8NLR0nv6vi0BYYKA== json-stringify-safe@5.0.1, json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: version "5.0.1" @@ -27897,11 +27897,16 @@ tslib@^2.0.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.0.1.tgz#410eb0d113e5b6356490eec749603725b021b43e" integrity sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ== -tslib@^2.0.1, tslib@~2.0.3: +tslib@^2.0.1: version "2.0.3" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.0.3.tgz#8e0741ac45fc0c226e58a17bfc3e64b9bc6ca61c" integrity sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ== +tslib@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a" + integrity sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A== + tsutils@^3.17.1: version "3.17.1" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759" @@ -28974,13 +28979,6 @@ vega-expression@^4.0.1, vega-expression@~4.0.1: dependencies: vega-util "^1.16.0" -vega-expression@~3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/vega-expression/-/vega-expression-3.0.0.tgz#39179d010b34c57513162bf1ab5a7bff4b31be91" - integrity sha512-/ObjIOK94MB+ziTuh8HZt2eWlKUPT/piRJLal5tx5QL1sQbfRi++7lHKTaKMLXLqc4Xqp9/DewE3PqQ6tYzaUA== - dependencies: - vega-util "^1.15.2" - vega-force@~4.0.7: version "4.0.7" resolved "https://registry.yarnpkg.com/vega-force/-/vega-force-4.0.7.tgz#6dc39ecb7889d9102661244d62fbc8d8714162ee" @@ -29051,10 +29049,10 @@ vega-label@~1.0.0: vega-scenegraph "^4.9.2" vega-util "^1.15.2" -vega-lite@^4.17.0: - version "4.17.0" - resolved "https://registry.yarnpkg.com/vega-lite/-/vega-lite-4.17.0.tgz#01ad4535e92f28c3852c1071711de272ddfb4631" - integrity sha512-MO2XsaVZqx6iWWmVA5vwYFamvhRUsKfVp7n0pNlkZ2/21cuxelSl92EePZ2YGmzL6z4/3K7r/45zaG8p+qNHeg== +vega-lite@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/vega-lite/-/vega-lite-5.0.0.tgz#93898a910702736da41048f590882b907d78ac65" + integrity sha512-CrMAy3D2E662qtShrOeGttwwthRxUOZUfdu39THyxkOfLNJBCLkNjfQpFekEidxwbtFTO1zMZzyFIP3AE2I8kQ== dependencies: "@types/clone" "~2.1.0" "@types/fast-json-stable-stringify" "^2.0.0" @@ -29062,12 +29060,12 @@ vega-lite@^4.17.0: clone "~2.1.2" fast-deep-equal "~3.1.3" fast-json-stable-stringify "~2.1.0" - json-stringify-pretty-compact "~2.0.0" - tslib "~2.0.3" + json-stringify-pretty-compact "~3.0.0" + tslib "~2.1.0" vega-event-selector "~2.0.6" - vega-expression "~3.0.0" + vega-expression "~4.0.1" vega-util "~1.16.0" - yargs "~16.0.3" + yargs "~16.2.0" vega-loader@^4.3.2, vega-loader@^4.3.3, vega-loader@~4.4.0: version "4.4.0" @@ -29174,12 +29172,12 @@ vega-time@^2.0.3, vega-time@^2.0.4, vega-time@~2.0.4: d3-time "^2.0.0" vega-util "^1.15.2" -vega-tooltip@^0.25.0: - version "0.25.0" - resolved "https://registry.yarnpkg.com/vega-tooltip/-/vega-tooltip-0.25.0.tgz#c5dcae1b2bd36e1c2e61e69f6ee7a0d0d27a3026" - integrity sha512-S48d/eP6WfieLmUvFEjd+raHWKKeK/RfTlwLa3zGcBULDHJY2NU2vRfjC1x33G6Y7eKeAfqGpM6ER5Qt1nf8tA== +vega-tooltip@^0.25.1: + version "0.25.1" + resolved "https://registry.yarnpkg.com/vega-tooltip/-/vega-tooltip-0.25.1.tgz#cb7e438438649eb46896e7bee6f54e25d25b3c09" + integrity sha512-ugGwGi2/p3OpB8N15xieuzP8DyV5DreqMWcmJ9zpWT8GlkyKtef4dGRXnvHeHQ+iJFmWrq4oZJ+kLTrdiECjAg== dependencies: - vega-util "^1.15.2" + vega-util "^1.16.0" vega-transforms@~4.9.3: version "4.9.3" @@ -30186,7 +30184,7 @@ y18n@^4.0.0: resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.1.tgz#8db2b83c31c5d75099bb890b23f3094891e247d4" integrity sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ== -y18n@^5.0.1, y18n@^5.0.5: +y18n@^5.0.5: version "5.0.5" resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.5.tgz#8769ec08d03b1ea2df2500acef561743bbb9ab18" integrity sha512-hsRUr4FFrvhhRH12wOdfs38Gy7k2FFzB9qgN9v3aLykRq0dRcdcpz5C9FxdS2NuhOrI/628b/KSTJ3rwHysYSg== @@ -30235,7 +30233,7 @@ yargs-parser@^18.1.2, yargs-parser@^18.1.3: camelcase "^5.0.0" decamelize "^1.2.0" -yargs-parser@^20.0.0, yargs-parser@^20.2.2: +yargs-parser@^20.2.2: version "20.2.2" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.2.tgz#84562c6b1c41ccec2f13d346c7dd83f8d1a0dc70" integrity sha512-XmrpXaTl6noDsf1dKpBuUNCOHqjs0g3jRMXf/ztRxdOmb+er8kE5z5b55Lz3p5u2T8KJ59ENBnASS8/iapVJ5g== @@ -30288,7 +30286,7 @@ yargs@^15.0.2, yargs@^15.3.1, yargs@^15.4.1: y18n "^4.0.0" yargs-parser "^18.1.2" -yargs@^16.0.3, yargs@^16.2.0: +yargs@^16.0.3, yargs@^16.2.0, yargs@~16.2.0: version "16.2.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== @@ -30333,19 +30331,6 @@ yargs@^7.1.0: y18n "^3.2.1" yargs-parser "5.0.0-security.0" -yargs@~16.0.3: - version "16.0.3" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.0.3.tgz#7a919b9e43c90f80d4a142a89795e85399a7e54c" - integrity sha512-6+nLw8xa9uK1BOEOykaiYAJVh6/CjxWXK/q9b5FpRgNslt8s22F2xMBqVIKgCRjNgGvGPBy8Vog7WN7yh4amtA== - dependencies: - cliui "^7.0.0" - escalade "^3.0.2" - get-caller-file "^2.0.5" - require-directory "^2.1.1" - string-width "^4.2.0" - y18n "^5.0.1" - yargs-parser "^20.0.0" - yargs@~3.10.0: version "3.10.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.10.0.tgz#f7ee7bd857dd7c1d2d38c0e74efbd681d1431fd1" From d29abdfa156eef0cdac35bb38dd86bffdbc70411 Mon Sep 17 00:00:00 2001 From: "Christiane (Tina) Heiligers" Date: Tue, 30 Mar 2021 07:54:15 -0700 Subject: [PATCH 12/32] [DOCS] Adds user-facing docs for the new KP logging configuration (#94993) Co-authored-by: Lisa Cawley --- .../core/application_service.asciidoc | 40 ++ .../core/configuration-service.asciidoc | 149 +++++ .../core/elasticsearch-service.asciidoc | 30 + .../architecture/core/http-service.asciidoc | 67 +++ .../architecture/core/index.asciidoc | 419 +------------- .../logging-configuration-migration.asciidoc | 84 +++ .../core/logging-service.asciidoc | 545 ++++++++++++++++++ .../core/patterns-scoped-services.asciidoc | 61 ++ .../core/saved-objects-service.asciidoc | 119 ++-- .../core/uisettings-service.asciidoc | 40 ++ docs/developer/architecture/index.asciidoc | 18 + docs/migration/migrate_8_0.asciidoc | 86 +-- docs/settings/logging-settings.asciidoc | 173 ++++++ docs/setup/settings.asciidoc | 121 ++-- .../reporting-troubleshooting.asciidoc | 4 +- 15 files changed, 1363 insertions(+), 593 deletions(-) create mode 100644 docs/developer/architecture/core/application_service.asciidoc create mode 100644 docs/developer/architecture/core/configuration-service.asciidoc create mode 100644 docs/developer/architecture/core/elasticsearch-service.asciidoc create mode 100644 docs/developer/architecture/core/http-service.asciidoc create mode 100644 docs/developer/architecture/core/logging-configuration-migration.asciidoc create mode 100644 docs/developer/architecture/core/logging-service.asciidoc create mode 100644 docs/developer/architecture/core/patterns-scoped-services.asciidoc create mode 100644 docs/developer/architecture/core/uisettings-service.asciidoc create mode 100644 docs/settings/logging-settings.asciidoc diff --git a/docs/developer/architecture/core/application_service.asciidoc b/docs/developer/architecture/core/application_service.asciidoc new file mode 100644 index 00000000000000..ba3c6bbed72bed --- /dev/null +++ b/docs/developer/architecture/core/application_service.asciidoc @@ -0,0 +1,40 @@ +[[application-service]] +== Application service +Kibana has migrated to be a Single Page Application. Plugins should use `Application service` API to instruct Kibana that an application should be loaded and rendered in the UI in response to user interactions. The service also provides utilities for controlling the navigation link state, seamlessly integrating routing between applications, and loading async chunks on demand. + +NOTE: The Application service is only available client side. + +[source,typescript] +---- +import { AppMountParameters, CoreSetup, Plugin, DEFAULT_APP_CATEGORIES } from 'kibana/public'; + +export class MyPlugin implements Plugin { + public setup(core: CoreSetup) { + core.application.register({ // <1> + category: DEFAULT_APP_CATEGORIES.kibana, + id: 'my-plugin', + title: 'my plugin title', + euiIconType: '/path/to/some.svg', + order: 100, + appRoute: '/app/my_plugin', // <2> + async mount(params: AppMountParameters) { // <3> + // Load application bundle + const { renderApp } = await import('./application'); + // Get start services + const [coreStart, depsStart] = await core.getStartServices(); // <4> + // Render the application + return renderApp(coreStart, depsStart, params); // <5> + }, + }); + } +} +---- +<1> See {kib-repo}blob/{branch}/docs/development/core/public/kibana-plugin-core-public.applicationsetup.register.md[application.register interface] +<2> Application specific URL. +<3> `mount` callback is invoked when a user navigates to the application-specific URL. +<4> `core.getStartServices` method provides API available during `start` lifecycle. +<5> `mount` method must return a function that will be called to unmount the application, which is called when Kibana unmounts the application. You can put a clean-up logic there. + +NOTE: you are free to use any UI library to render a plugin application in DOM. +However, we recommend using React and https://elastic.github.io/eui[EUI] for all your basic UI +components to create a consistent UI experience. diff --git a/docs/developer/architecture/core/configuration-service.asciidoc b/docs/developer/architecture/core/configuration-service.asciidoc new file mode 100644 index 00000000000000..031135c7b790f9 --- /dev/null +++ b/docs/developer/architecture/core/configuration-service.asciidoc @@ -0,0 +1,149 @@ +[[configuration-service]] +== Configuration service +{kib} provides `ConfigService` for plugin developers that want to support +adjustable runtime behavior for their plugins. +Plugins can only read their own configuration values, it is not possible to access the configuration values from {kib} Core or other plugins directly. + +NOTE: The Configuration service is only available server side. + +[source,js] +---- +// in Legacy platform +const basePath = config.get('server.basePath'); +// in Kibana Platform 'basePath' belongs to the http service +const basePath = core.http.basePath.get(request); +---- + +To have access to your plugin config, you _should_: + +* Declare plugin-specific `configPath` (will fallback to plugin `id` +if not specified) in {kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.pluginmanifest.md[`kibana.json`] manifest file. +* Export schema validation for the config from plugin's main file. Schema is +mandatory. If a plugin reads from the config without schema declaration, +`ConfigService` will throw an error. + +*my_plugin/server/index.ts* +[source,typescript] +---- +import { schema, TypeOf } from '@kbn/config-schema'; +export const plugin = … +export const config = { + schema: schema.object(…), +}; +export type MyPluginConfigType = TypeOf; +---- + +* Read config value exposed via `PluginInitializerContext`: + +*my_plugin/server/index.ts* +[source,typescript] +---- +import type { PluginInitializerContext } from 'kibana/server'; +export class MyPlugin { + constructor(initializerContext: PluginInitializerContext) { + this.config$ = initializerContext.config.create(); + // or if config is optional: + this.config$ = initializerContext.config.createIfExists(); + } + ... +} +---- + +If your plugin also has a client-side part, you can also expose +configuration properties to it using the configuration `exposeToBrowser` +allow-list property. + +*my_plugin/server/index.ts* +[source,typescript] +---- +import { schema, TypeOf } from '@kbn/config-schema'; +import type { PluginConfigDescriptor } from 'kibana/server'; + +const configSchema = schema.object({ + secret: schema.string({ defaultValue: 'Only on server' }), + uiProp: schema.string({ defaultValue: 'Accessible from client' }), +}); + +type ConfigType = TypeOf; + +export const config: PluginConfigDescriptor = { + exposeToBrowser: { + uiProp: true, + }, + schema: configSchema, +}; +---- + +Configuration containing only the exposed properties will be then +available on the client-side using the plugin's `initializerContext`: + +*my_plugin/public/index.ts* +[source,typescript] +---- +interface ClientConfigType { + uiProp: string; +} + +export class MyPlugin implements Plugin { + constructor(private readonly initializerContext: PluginInitializerContext) {} + + public async setup(core: CoreSetup, deps: {}) { + const config = this.initializerContext.config.get(); + } +---- + +All plugins are considered enabled by default. If you want to disable +your plugin, you could declare the `enabled` flag in the plugin +config. This is a special {kib} Platform key. {kib} reads its +value and won’t create a plugin instance if `enabled: false`. + +[source,js] +---- +export const config = { + schema: schema.object({ enabled: schema.boolean({ defaultValue: false }) }), +}; +---- +[[handle-plugin-configuration-deprecations]] +=== Handle plugin configuration deprecations +If your plugin has deprecated configuration keys, you can describe them using +the `deprecations` config descriptor field. +Deprecations are managed on a per-plugin basis, meaning you don’t need to specify +the whole property path, but use the relative path from your plugin’s +configuration root. + +*my_plugin/server/index.ts* +[source,typescript] +---- +import { schema, TypeOf } from '@kbn/config-schema'; +import type { PluginConfigDescriptor } from 'kibana/server'; + +const configSchema = schema.object({ + newProperty: schema.string({ defaultValue: 'Some string' }), +}); + +type ConfigType = TypeOf; + +export const config: PluginConfigDescriptor = { + schema: configSchema, + deprecations: ({ rename, unused }) => [ + rename('oldProperty', 'newProperty'), + unused('someUnusedProperty'), + ], +}; +---- + +In some cases, accessing the whole configuration for deprecations is +necessary. For these edge cases, `renameFromRoot` and `unusedFromRoot` +are also accessible when declaring deprecations. + +*my_plugin/server/index.ts* +[source,typescript] +---- +export const config: PluginConfigDescriptor = { + schema: configSchema, + deprecations: ({ renameFromRoot, unusedFromRoot }) => [ + renameFromRoot('oldplugin.property', 'myplugin.property'), + unusedFromRoot('oldplugin.deprecated'), + ], +}; +---- diff --git a/docs/developer/architecture/core/elasticsearch-service.asciidoc b/docs/developer/architecture/core/elasticsearch-service.asciidoc new file mode 100644 index 00000000000000..55632c01179381 --- /dev/null +++ b/docs/developer/architecture/core/elasticsearch-service.asciidoc @@ -0,0 +1,30 @@ +[[elasticsearch-service]] +== Elasticsearch service +`Elasticsearch service` provides `elasticsearch.client` program API to communicate with Elasticsearch server HTTP API. + +NOTE: The Elasticsearch service is only available server side. You can use the {kib-repo}blob/{branch}/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md[Data plugin] APIs on the client side. + +`elasticsearch.client` interacts with Elasticsearch service on behalf of: + +- `kibana_system` user via `elasticsearch.client.asInternalUser.*` methods. +- a current end-user via `elasticsearch.client.asCurrentUser.*` methods. In this case Elasticsearch client should be given the current user credentials. +See <> and <>. + +{kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicestart.md[Elasticsearch service API docs] + +[source,typescript] +---- +import { CoreStart, Plugin } from 'kibana/public'; + +export class MyPlugin implements Plugin { + public start(core: CoreStart) { + async function asyncTask() { + const result = await core.elasticsearch.client.asInternalUser.ping(…); + } + asyncTask(); + } +} +---- + +For advanced use-cases, such as a search, use {kib-repo}blob/{branch}/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md[Data plugin] + diff --git a/docs/developer/architecture/core/http-service.asciidoc b/docs/developer/architecture/core/http-service.asciidoc new file mode 100644 index 00000000000000..45468d618dd096 --- /dev/null +++ b/docs/developer/architecture/core/http-service.asciidoc @@ -0,0 +1,67 @@ +[[http-service]] +== HTTP service + +NOTE: The HTTP service is available both server and client side. + +=== Server side usage + +The server-side HttpService allows server-side plugins to register endpoints with built-in support for request validation. These endpoints may be used by client-side code or be exposed as a public API for users. Most plugins integrate directly with this service. + +The service allows plugins to: +* to extend the {kib} server with custom HTTP API. +* to execute custom logic on an incoming request or server response. +* implement custom authentication and authorization strategy. + +See {kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.httpservicesetup.md[HTTP service API docs] + +[source,typescript] +---- +import { schema } from '@kbn/config-schema'; +import type { CoreSetup, Plugin } from 'kibana/server'; + +export class MyPlugin implements Plugin { + public setup(core: CoreSetup) { + const router = core.http.createRouter(); + + const validate = { + params: schema.object({ + id: schema.string(), + }), + }; + + router.get({ + path: 'my_plugin/{id}', + validate + }, + async (context, request, response) => { + const data = await findObject(request.params.id); + if (!data) return response.notFound(); + return response.ok({ + body: data, + headers: { + 'content-type': 'application/json' + } + }); + }); + } +} +---- + +=== Client side usage + +The HTTP service is also offered on the client side and provides an API to communicate with the {kib} server via HTTP interface. +The client-side HttpService is a preconfigured wrapper around `window.fetch` that includes some default behavior and automatically handles common errors (such as session expiration). The service should only be used for access to backend endpoints registered by the same plugin. Feel free to use another HTTP client library to request 3rd party services. + +[source,typescript] +---- +import { CoreStart } from 'kibana/public'; +interface ResponseType {…}; +interface MyPluginData {…}; +async function fetchData(core: CoreStart) { + return await core.http.get( + '/api/my_plugin/', + { query: … }, + ); +} +---- +See {kib-repo}blob/{branch}/docs/development/core/public/kibana-plugin-core-public.httpsetup.md[for all available API]. diff --git a/docs/developer/architecture/core/index.asciidoc b/docs/developer/architecture/core/index.asciidoc index 4a86c90cf8c107..53720a593d3f2d 100644 --- a/docs/developer/architecture/core/index.asciidoc +++ b/docs/developer/architecture/core/index.asciidoc @@ -27,421 +27,18 @@ export class MyPlugin { } ---- -=== Server-side -[[configuration-service]] -==== Configuration service -{kib} provides `ConfigService` if a plugin developer may want to support -adjustable runtime behavior for their plugins. -Plugins can only read their own configuration values, it is not possible to access the configuration values from {kib} Core or other plugins directly. +The services that core provides are: -[source,js] ----- -// in Legacy platform -const basePath = config.get('server.basePath'); -// in Kibana Platform 'basePath' belongs to the http service -const basePath = core.http.basePath.get(request); ----- - -To have access to your plugin config, you _should_: +* <> +* <> +* <> +* <> +* <> +* <> +* <> -* Declare plugin-specific `configPath` (will fallback to plugin `id` -if not specified) in {kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.pluginmanifest.md[`kibana.json`] manifest file. -* Export schema validation for the config from plugin's main file. Schema is -mandatory. If a plugin reads from the config without schema declaration, -`ConfigService` will throw an error. - -*my_plugin/server/index.ts* -[source,typescript] ----- -import { schema, TypeOf } from '@kbn/config-schema'; -export const plugin = … -export const config = { - schema: schema.object(…), -}; -export type MyPluginConfigType = TypeOf; ----- - -* Read config value exposed via `PluginInitializerContext`. -*my_plugin/server/index.ts* -[source,typescript] ----- -import type { PluginInitializerContext } from 'kibana/server'; -export class MyPlugin { - constructor(initializerContext: PluginInitializerContext) { - this.config$ = initializerContext.config.create(); - // or if config is optional: - this.config$ = initializerContext.config.createIfExists(); - } ----- - -If your plugin also has a client-side part, you can also expose -configuration properties to it using the configuration `exposeToBrowser` -allow-list property. - -*my_plugin/server/index.ts* -[source,typescript] ----- -import { schema, TypeOf } from '@kbn/config-schema'; -import type { PluginConfigDescriptor } from 'kibana/server'; -const configSchema = schema.object({ - secret: schema.string({ defaultValue: 'Only on server' }), - uiProp: schema.string({ defaultValue: 'Accessible from client' }), -}); -type ConfigType = TypeOf; -export const config: PluginConfigDescriptor = { - exposeToBrowser: { - uiProp: true, - }, - schema: configSchema, -}; ----- - -Configuration containing only the exposed properties will be then -available on the client-side using the plugin's `initializerContext`: - -*my_plugin/public/index.ts* -[source,typescript] ----- -interface ClientConfigType { - uiProp: string; -} - -export class MyPlugin implements Plugin { - constructor(private readonly initializerContext: PluginInitializerContext) {} - - public async setup(core: CoreSetup, deps: {}) { - const config = this.initializerContext.config.get(); - } ----- - -All plugins are considered enabled by default. If you want to disable -your plugin, you could declare the `enabled` flag in the plugin -config. This is a special {kib} Platform key. {kib} reads its -value and won’t create a plugin instance if `enabled: false`. - -[source,js] ----- -export const config = { - schema: schema.object({ enabled: schema.boolean({ defaultValue: false }) }), -}; ----- -[[handle-plugin-configuration-deprecations]] -===== Handle plugin configuration deprecations -If your plugin has deprecated configuration keys, you can describe them using -the `deprecations` config descriptor field. -Deprecations are managed on a per-plugin basis, meaning you don’t need to specify -the whole property path, but use the relative path from your plugin’s -configuration root. - -*my_plugin/server/index.ts* -[source,typescript] ----- -import { schema, TypeOf } from '@kbn/config-schema'; -import type { PluginConfigDescriptor } from 'kibana/server'; - -const configSchema = schema.object({ - newProperty: schema.string({ defaultValue: 'Some string' }), -}); - -type ConfigType = TypeOf; - -export const config: PluginConfigDescriptor = { - schema: configSchema, - deprecations: ({ rename, unused }) => [ - rename('oldProperty', 'newProperty'), - unused('someUnusedProperty'), - ], -}; ----- - -In some cases, accessing the whole configuration for deprecations is -necessary. For these edge cases, `renameFromRoot` and `unusedFromRoot` -are also accessible when declaring deprecations. - -*my_plugin/server/index.ts* -[source,typescript] ----- -export const config: PluginConfigDescriptor = { - schema: configSchema, - deprecations: ({ renameFromRoot, unusedFromRoot }) => [ - renameFromRoot('oldplugin.property', 'myplugin.property'), - unusedFromRoot('oldplugin.deprecated'), - ], -}; ----- -==== Logging service -Allows a plugin to provide status and diagnostic information. -For detailed instructions see the {kib-repo}blob/{branch}/src/core/server/logging/README.md[logging service documentation]. - -[source,typescript] ----- -import type { PluginInitializerContext, CoreSetup, Plugin, Logger } from 'kibana/server'; - -export class MyPlugin implements Plugin { - private readonly logger: Logger; - - constructor(initializerContext: PluginInitializerContext) { - this.logger = initializerContext.logger.get(); - } - - public setup(core: CoreSetup) { - try { - this.logger.debug('doing something...'); - // … - } catch (e) { - this.logger.error('failed doing something...'); - } - } -} ----- - -==== Elasticsearch service -`Elasticsearch service` provides `elasticsearch.client` program API to communicate with Elasticsearch server REST API. -`elasticsearch.client` interacts with Elasticsearch service on behalf of: - -- `kibana_system` user via `elasticsearch.client.asInternalUser.*` methods. -- a current end-user via `elasticsearch.client.asCurrentUser.*` methods. In this case Elasticsearch client should be given the current user credentials. -See <> and <>. - -{kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicestart.md[Elasticsearch service API docs] - -[source,typescript] ----- -import { CoreStart, Plugin } from 'kibana/public'; - -export class MyPlugin implements Plugin { - public start(core: CoreStart) { - async function asyncTask() { - const result = await core.elasticsearch.client.asInternalUser.ping(…); - } - asyncTask(); - } -} ----- -For advanced use-cases, such as a search, use {kib-repo}blob/{branch}/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md[Data plugin] -include::saved-objects-service.asciidoc[leveloffset=+1] - -==== HTTP service -Allows plugins: - -* to extend the {kib} server with custom REST API. -* to execute custom logic on an incoming request or server response. -* implement custom authentication and authorization strategy. - -See {kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.httpservicesetup.md[HTTP service API docs] - -[source,typescript] ----- -import { schema } from '@kbn/config-schema'; -import type { CoreSetup, Plugin } from 'kibana/server'; - -export class MyPlugin implements Plugin { - public setup(core: CoreSetup) { - const router = core.http.createRouter(); - - const validate = { - params: schema.object({ - id: schema.string(), - }), - }; - - router.get({ - path: 'my_plugin/{id}', - validate - }, - async (context, request, response) => { - const data = await findObject(request.params.id); - if (!data) return response.notFound(); - return response.ok({ - body: data, - headers: { - 'content-type': 'application/json' - } - }); - }); - } -} ----- - -==== UI settings service -The program interface to <>. -It makes it possible for Kibana plugins to extend Kibana UI Settings Management with custom settings. - -See: - -- {kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.uisettingsservicesetup.register.md[UI settings service Setup API docs] -- {kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.uisettingsservicestart.register.md[UI settings service Start API docs] - -[source,typescript] ----- -import { schema } from '@kbn/config-schema'; -import type { CoreSetup,Plugin } from 'kibana/server'; - -export class MyPlugin implements Plugin { - public setup(core: CoreSetup) { - core.uiSettings.register({ - custom: { - value: '42', - schema: schema.string(), - }, - }); - const router = core.http.createRouter(); - router.get({ - path: 'my_plugin/{id}', - validate: …, - }, - async (context, request, response) => { - const customSetting = await context.uiSettings.client.get('custom'); - … - }); - } -} - ----- - -=== Client-side -==== Application service -Kibana has migrated to be a Single Page Application. Plugins should use `Application service` API to instruct Kibana what an application should be loaded & rendered in the UI in response to user interactions. -[source,typescript] ----- -import { AppMountParameters, CoreSetup, Plugin, DEFAULT_APP_CATEGORIES } from 'kibana/public'; - -export class MyPlugin implements Plugin { - public setup(core: CoreSetup) { - core.application.register({ // <1> - category: DEFAULT_APP_CATEGORIES.kibana, - id: 'my-plugin', - title: 'my plugin title', - euiIconType: '/path/to/some.svg', - order: 100, - appRoute: '/app/my_plugin', // <2> - async mount(params: AppMountParameters) { // <3> - // Load application bundle - const { renderApp } = await import('./application'); - // Get start services - const [coreStart, depsStart] = await core.getStartServices(); // <4> - // Render the application - return renderApp(coreStart, depsStart, params); // <5> - }, - }); - } -} ----- -<1> See {kib-repo}blob/{branch}/docs/development/core/public/kibana-plugin-core-public.applicationsetup.register.md[application.register interface] -<2> Application specific URL. -<3> `mount` callback is invoked when a user navigates to the application-specific URL. -<4> `core.getStartServices` method provides API available during `start` lifecycle. -<5> `mount` method must return a function that will be called to unmount the application. - -NOTE:: you are free to use any UI library to render a plugin application in DOM. -However, we recommend using React and https://elastic.github.io/eui[EUI] for all your basic UI -components to create a consistent UI experience. - -==== HTTP service -Provides API to communicate with the {kib} server. Feel free to use another HTTP client library to request 3rd party services. - -[source,typescript] ----- -import { CoreStart } from 'kibana/public'; -interface ResponseType {…}; -async function fetchData(core: CoreStart) { - return await core.http.get<>( - '/api/my_plugin/', - { query: … }, - ); -} ----- -See {kib-repo}blob/{branch}/docs/development/core/public/kibana-plugin-core-public.httpsetup.md[for all available API]. - -==== Elasticsearch service -Not available in the browser. Use {kib-repo}blob/{branch}/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md[Data plugin] instead. - -== Patterns -[[scoped-services]] -=== Scoped services -Whenever Kibana needs to get access to data saved in elasticsearch, it -should perform a check whether an end-user has access to the data. In -the legacy platform, Kibana requires binding elasticsearch related API -with an incoming request to access elasticsearch service on behalf of a -user. - -[source,js] ----- -async function handler(req, res) { - const dataCluster = server.plugins.elasticsearch.getCluster('data'); - const data = await dataCluster.callWithRequest(req, 'ping'); -} ----- - -The Kibana Platform introduced a handler interface on the server-side to perform that association -internally. Core services, that require impersonation with an incoming -request, are exposed via `context` argument of -{kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.requesthandler.md[the -request handler interface.] The above example looks in the Kibana Platform -as - -[source,js] ----- -async function handler(context, req, res) { - const data = await context.core.elasticsearch.client.asCurrentUser('ping'); -} ----- - -The -{kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.requesthandlercontext.md[request -handler context] exposed the next scoped *core* services: - -[width="100%",cols="30%,70%",options="header",] -|=== -|Legacy Platform |Kibana Platform -|`request.getSavedObjectsClient` -|{kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.savedobjectsclient.md[`context.savedObjects.client`] - -|`server.plugins.elasticsearch.getCluster('admin')` -|{kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.iscopedclusterclient.md[`context.elasticsearch.client`] - -|`server.plugins.elasticsearch.getCluster('data')` -|{kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.iscopedclusterclient.md[`context.elasticsearch.client`] - -|`request.getUiSettingsService` -|{kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.iuisettingsclient.md[`context.uiSettings.client`] -|=== - -==== Declare a custom scoped service - -Plugins can extend the handler context with a custom API that will be -available to the plugin itself and all dependent plugins. For example, -the plugin creates a custom elasticsearch client and wants to use it via -the request handler context: - -[source,typescript] ----- -import type { CoreSetup, RequestHandlerContext, IScopedClusterClient } from 'kibana/server'; - -interface MyRequestHandlerContext extends RequestHandlerContext { - myPlugin: { - client: IScopedClusterClient; - }; -} - -class MyPlugin { - setup(core: CoreSetup) { - const client = core.elasticsearch.createClient('myClient'); - core.http.registerRouteHandlerContext('myPlugin', (context, req, res) => { - return { client: client.asScoped(req) }; - }); - const router = core.http.createRouter(); - router.get( - { path: '/api/my-plugin/', validate: … }, - async (context, req, res) => { - // context type is inferred as MyPluginContext - const data = await context.myPlugin.client.asCurrentUser('endpoint'); - } - ); - } ----- diff --git a/docs/developer/architecture/core/logging-configuration-migration.asciidoc b/docs/developer/architecture/core/logging-configuration-migration.asciidoc new file mode 100644 index 00000000000000..19f10a881d5e8a --- /dev/null +++ b/docs/developer/architecture/core/logging-configuration-migration.asciidoc @@ -0,0 +1,84 @@ +[[logging-configuration-migration]] +== Logging configuration migration + +Compatibility with the legacy logging system is assured until the end of the `v7` version. +All log messages handled by `root` context are forwarded to the legacy logging service. If you re-write +root appenders, make sure that it contains `default` appender to provide backward compatibility. + +NOTE: When you switch to the new logging configuration, you will start seeing duplicate log entries in both formats. +These will be removed when the `default` appender is no longer required. If you define an appender for a logger, +the log messages aren't handled by the `root` logger anymore and are not forwarded to the legacy logging service. + +[[logging-pattern-format-old-and-new-example]] +[options="header"] +|=== + +| Parameter | Platform log record in **pattern** format | Legacy Platform log record **text** format + +| @timestamp | ISO8601_TZ `2012-01-31T23:33:22.011-05:00` | Absolute `23:33:22.011` + +| logger | `parent.child` | `['parent', 'child']` + +| level | `DEBUG` | `['debug']` + +| meta | stringified JSON object `{"to": "v8"}`| N/A + +| pid | can be configured as `%pid` | N/A + +|=== + +[[logging-json-format-old-and-new-example]] +[options="header"] +|=== + +| Parameter | Platform log record in **json** format | Legacy Platform log record **json** format + +| @timestamp | ISO8601_TZ `2012-01-31T23:33:22.011-05:00` | ISO8601 `2012-01-31T23:33:22.011Z` + +| logger | `log.logger: parent.child` | `tags: ['parent', 'child']` + +| level | `log.level: DEBUG` | `tags: ['debug']` + +| meta | merged in log record `{... "to": "v8"}` | merged in log record `{... "to": "v8"}` + +| pid | `process.pid: 12345` | `pid: 12345` + +| type | N/A | `type: log` + +| error | `{ message, name, stack }` | `{ message, name, stack, code, signal }` + +|=== + +[[logging-cli-migration]] +=== Logging configuration via CLI + +As is the case for any of Kibana's config settings, you can specify your logging configuration via the CLI. For convenience, the `--verbose` and `--silent` flags exist as shortcuts and will continue to be supported beyond v7. + +If you wish to override these flags, you can always do so by passing your preferred logging configuration directly to the CLI. For example, with the following configuration: + +[source,yaml] +---- +logging: + appenders: + custom: + type: console + layout: + type: pattern + pattern: "[%date][%level] %message" +---- + +you can override the flags with: + +[options="header"] +|=== + +| legacy logging | {kib} Platform logging | cli shortcuts + +|--verbose| --logging.root.level=debug --logging.root.appenders[0]=default --logging.root.appenders[1]=custom | --verbose + +|--quiet| --logging.root.level=error --logging.root.appenders[0]=default --logging.root.appenders[1]=custom | not supported + +|--silent| --logging.root.level=off | --silent +|=== + +NOTE: To preserve backwards compatibility, you are required to pass the root `default` appender until the legacy logging system is removed in `v8.0`. diff --git a/docs/developer/architecture/core/logging-service.asciidoc b/docs/developer/architecture/core/logging-service.asciidoc new file mode 100644 index 00000000000000..7dc2a4ca1f4cea --- /dev/null +++ b/docs/developer/architecture/core/logging-service.asciidoc @@ -0,0 +1,545 @@ +[[logging-service]] +== Logging service +Allows a plugin to provide status and diagnostic information. + +NOTE: The Logging service is only available server side. + +[source,typescript] +---- +import type { PluginInitializerContext, CoreSetup, Plugin, Logger } from 'kibana/server'; + +export class MyPlugin implements Plugin { + private readonly logger: Logger; + + constructor(initializerContext: PluginInitializerContext) { + this.logger = initializerContext.logger.get(); + } + + public setup(core: CoreSetup) { + try { + this.logger.debug('doing something...'); + // … + } catch (e) { + this.logger.error('failed doing something...'); + } + } +} +---- + +The way logging works in {kib} is inspired by the `log4j 2` logging framework used by {ref-bare}/current/logging.html[Elasticsearch]. +The main idea is to have consistent logging behavior (configuration, log format etc.) across the entire Elastic Stack where possible. + +=== Loggers, Appenders and Layouts + +The {kib} logging system has three main components: _loggers_, _appenders_ and _layouts_. These components allow us to log +messages according to message type and level, to control how these messages are formatted and where the final logs +will be displayed or stored. + +__Loggers__ define what logging settings should be applied to a particular logger. + +__<>__ define where log messages are displayed (eg. stdout or console) and stored (eg. file on the disk). + +__<>__ define how log messages are formatted and what type of information they include. + +[[log-level]] +=== Log level + +Currently we support the following log levels: _all_, _fatal_, _error_, _warn_, _info_, _debug_, _trace_, _off_. + +Levels are ordered, so _all_ > _fatal_ > _error_ > _warn_ > _info_ > _debug_ > _trace_ > _off_. + +A log record is being logged by the logger if its level is higher than or equal to the level of its logger. Otherwise, +the log record is ignored. + +The _all_ and _off_ levels can be used only in configuration and are just handy shortcuts that allow you to log every +log record or disable logging entirely or for a specific logger. These levels are also configurable as <>. + +[[logging-layouts]] +=== Layouts + +Every appender should know exactly how to format log messages before they are written to the console or file on the disk. +This behavior is controlled by the layouts and configured through `appender.layout` configuration property for every +custom appender. Currently we don't define any default layout for the +custom appenders, so one should always make the choice explicitly. + +There are two types of layout supported at the moment: <> and <>. + +[[pattern-layout]] +==== Pattern layout + +With `pattern` layout it's possible to define a string pattern with special placeholders `%conversion_pattern` that will be replaced with data from the actual log message. By default the following pattern is used: `[%date][%level][%logger] %message`. + +NOTE: The `pattern` layout uses a sub-set of https://logging.apache.org/log4j/2.x/manual/layouts.html#PatternLayout[log4j 2 pattern syntax] and **doesn't implement** all `log4j 2` capabilities. + +The conversions that are provided out of the box are: + +**level** +Outputs the <> of the logging event. +Example of `%level` output: `TRACE`, `DEBUG`, `INFO`. + +**logger** +Outputs the name of the logger that published the logging event. +Example of `%logger` output: `server`, `server.http`, `server.http.kibana`. + +**message** +Outputs the application supplied message associated with the logging event. + +**meta** +Outputs the entries of `meta` object data in **json** format, if one is present in the event. +Example of `%meta` output: +[source,bash] +---- +// Meta{from: 'v7', to: 'v8'} +'{"from":"v7","to":"v8"}' +// Meta empty object +'{}' +// no Meta provided +'' +---- + +[[date-format]] +**date** +Outputs the date of the logging event. The date conversion specifier may be followed by a set of braces containing a name of predefined date format and canonical timezone name. +Timezone name is expected to be one from https://en.wikipedia.org/wiki/List_of_tz_database_time_zones[TZ database name]. +Timezone defaults to the host timezone when not explicitly specified. +Example of `%date` output: + +[[date-conversion-pattern-examples]] +[options="header"] +|=== + +| Conversion pattern | Example + +| `%date` +| `2012-02-01T14:30:22.011Z` uses `ISO8601` format by default + +| `%date{ISO8601}` +| `2012-02-01T14:30:22.011Z` + +| `%date{ISO8601_TZ}` +| `2012-02-01T09:30:22.011-05:00` `ISO8601` with timezone + +| `%date{ISO8601_TZ}{America/Los_Angeles}` +| `2012-02-01T06:30:22.011-08:00` + +| `%date{ABSOLUTE}` +| `09:30:22.011` + +| `%date{ABSOLUTE}{America/Los_Angeles}` +| `06:30:22.011` + +| `%date{UNIX}` +| `1328106622` + +| `%date{UNIX_MILLIS}` +| `1328106622011` + +|=== + +**pid** +Outputs the process ID. + +The pattern layout also offers a `highlight` option that allows you to highlight +some parts of the log message with different colors. Highlighting is quite handy if log messages are forwarded +to a terminal with color support. + +[[json-layout]] +==== JSON layout +With `json` layout log messages will be formatted as JSON strings in https://www.elastic.co/guide/en/ecs/current/ecs-reference.html[ECS format] that includes a timestamp, log level, logger, message text and any other metadata that may be associated with the log message itself. + +[[logging-appenders]] +=== Appenders + +[[rolling-file-appender]] +==== Rolling File Appender + +Similar to Log4j's `RollingFileAppender`, this appender will log into a file, and rotate it following a rolling +strategy when the configured policy triggers. + +===== Triggering Policies + +The triggering policy determines when a rollover should occur. + +There are currently two policies supported: `size-limit` and `time-interval`. + +[[size-limit-triggering-policy]] +**SizeLimitTriggeringPolicy** + +This policy will rotate the file when it reaches a predetermined size. + +[source,yaml] +---- +logging: + appenders: + rolling-file: + type: rolling-file + fileName: /var/logs/kibana.log + policy: + type: size-limit + size: 50mb + strategy: + //... + layout: + type: pattern +---- + +The options are: + +- `size` + +The maximum size the log file should reach before a rollover should be performed. The default value is `100mb` + +[[time-interval-triggering-policy]] +**TimeIntervalTriggeringPolicy** + +This policy will rotate the file every given interval of time. + +[source,yaml] +---- +logging: + appenders: + rolling-file: + type: rolling-file + fileName: /var/logs/kibana.log + policy: + type: time-interval + interval: 10s + modulate: true + strategy: + //... + layout: + type: pattern +---- + +The options are: + +- `interval` + +How often a rollover should occur. The default value is `24h` + +- `modulate` + +Whether the interval should be adjusted to cause the next rollover to occur on the interval boundary. + +For example, if modulate is true and the interval is `4h`, if the current hour is 3 am then the first rollover will occur at 4 am +and then next ones will occur at 8 am, noon, 4pm, etc. The default value is `true`. + +===== Rolling strategies + +The rolling strategy determines how the rollover should occur: both the naming of the rolled files, +and their retention policy. + +There is currently one strategy supported: `numeric`. + +**NumericRollingStrategy** + +This strategy will suffix the file with a given pattern when rolling, +and will retains a fixed amount of rolled files. + +[source,yaml] +---- +logging: + appenders: + rolling-file: + type: rolling-file + fileName: /var/logs/kibana.log + policy: + // ... + strategy: + type: numeric + pattern: '-%i' + max: 2 + layout: + type: pattern +---- + +For example, with this configuration: + +- During the first rollover kibana.log is renamed to kibana-1.log. A new kibana.log file is created and starts + being written to. +- During the second rollover kibana-1.log is renamed to kibana-2.log and kibana.log is renamed to kibana-1.log. + A new kibana.log file is created and starts being written to. +- During the third and subsequent rollovers, kibana-2.log is deleted, kibana-1.log is renamed to kibana-2.log and + kibana.log is renamed to kibana-1.log. A new kibana.log file is created and starts being written to. + +The options are: + +- `pattern` + +The suffix to append to the file path when rolling. Must include `%i`, as this is the value +that will be converted to the file index. + +For example, with `fileName: /var/logs/kibana.log` and `pattern: '-%i'`, the rolling files created +will be `/var/logs/kibana-1.log`, `/var/logs/kibana-2.log`, and so on. The default value is `-%i` + +- `max` + +The maximum number of files to keep. Once this number is reached, oldest files will be deleted. The default value is `7` + +==== Rewrite Appender + +WARNING: This appender is currently considered experimental and is not intended +for public consumption. The API is subject to change at any time. + +Similar to log4j's `RewriteAppender`, this appender serves as a sort of middleware, +modifying the provided log events before passing them along to another +appender. + +[source,yaml] +---- +logging: + appenders: + my-rewrite-appender: + type: rewrite + appenders: [console, file] # name of "destination" appender(s) + policy: + # ... +---- + +The most common use case for the `RewriteAppender` is when you want to +filter or censor sensitive data that may be contained in a log entry. +In fact, with a default configuration, {kib} will automatically redact +any `authorization`, `cookie`, or `set-cookie` headers when logging http +requests & responses. + +To configure additional rewrite rules, you'll need to specify a <>. + +[[rewrite-policies]] +===== Rewrite Policies + +Rewrite policies exist to indicate which parts of a log record can be +modified within the rewrite appender. + +**Meta** + +The `meta` rewrite policy can read and modify any data contained in the +`LogMeta` before passing it along to a destination appender. + +Meta policies must specify one of three modes, which indicate which action +to perform on the configured properties: +- `update` updates an existing property at the provided `path`. +- `remove` removes an existing property at the provided `path`. + +The `properties` are listed as a `path` and `value` pair, where `path` is +the dot-delimited path to the target property in the `LogMeta` object, and +`value` is the value to add or update in that target property. When using +the `remove` mode, a `value` is not necessary. + +Here's an example of how you would replace any `cookie` header values with `[REDACTED]`: + +[source,yaml] +---- +logging: + appenders: + my-rewrite-appender: + type: rewrite + appenders: [console] + policy: + type: meta # indicates that we want to rewrite the LogMeta + mode: update # will update an existing property only + properties: + - path: "http.request.headers.cookie" # path to property + value: "[REDACTED]" # value to replace at path +---- + +Rewrite appenders can even be passed to other rewrite appenders to apply +multiple filter policies/modes, as long as it doesn't create a circular +reference. Each rewrite appender is applied sequentially (one after the other). + +[source,yaml] +---- +logging: + appenders: + remove-request-headers: + type: rewrite + appenders: [censor-response-headers] # redirect to the next rewrite appender + policy: + type: meta + mode: remove + properties: + - path: "http.request.headers" # remove all request headers + censor-response-headers: + type: rewrite + appenders: [console] # output to console + policy: + type: meta + mode: update + properties: + - path: "http.response.headers.set-cookie" + value: "[REDACTED]" +---- + +===== Complete Example For Rewrite Appender + +[source,yaml] +---- +logging: + appenders: + custom_console: + type: console + layout: + type: pattern + highlight: true + pattern: "[%date][%level][%logger] %message %meta" + file: + type: file + fileName: ./kibana.log + layout: + type: json + censor: + type: rewrite + appenders: [custom_console, file] + policy: + type: meta + mode: update + properties: + - path: "http.request.headers.cookie" + value: "[REDACTED]" + loggers: + - name: http.server.response + appenders: [censor] # pass these logs to our rewrite appender + level: debug +---- + +[[logger-hierarchy]] +=== Logger hierarchy + +Every logger has a unique name that follows a hierarchical naming rule. The logger is considered to be an +ancestor of another logger if its name followed by a `.` is a prefix of the descendant logger. For example, a logger +named `a.b` is an ancestor of logger `a.b.c`. All top-level loggers are descendants of a special `root` logger at the top of the logger hierarchy. The `root` logger always exists and +fully configured. + +You can configure _<>_ and _appenders_ for a specific logger. If a logger only has a _log level_ configured, then the _appenders_ configuration applied to the logger is inherited from the ancestor logger. + +NOTE: In the current implementation we __don't support__ so called _appender additivity_ when log messages are forwarded to _every_ distinct appender within the +ancestor chain including `root`. That means that log messages are only forwarded to appenders that are configured for a particular logger. If a logger doesn't have any appenders configured, the configuration of that particular logger will be inherited from its closest ancestor. + +[[dedicated-loggers]] +==== Dedicated loggers + +**Root** + +The `root` logger has a dedicated configuration node since this logger is special and should always exist. By default `root` is configured with `info` level and `default` appender that is also always available. This is the configuration that all custom loggers will use unless they're re-configured explicitly. + +For example to see _all_ log messages that fall back on the `root` logger configuration, just add one line to the configuration: + +[source,yaml] +---- +logging.root.level: all +---- + +Or disable logging entirely with `off`: + +[source,yaml] +---- +logging.root.level: off +---- + +**Metrics Logs** + +The `metrics.ops` logger is configured with `debug` level and will automatically output sample system and process information at a regular interval. +The metrics that are logged are a subset of the data collected and are formatted in the log message as follows: + +[options="header"] +|=== + +| Ops formatted log property | Location in metrics service | Log units + +| memory | process.memory.heap.used_in_bytes | http://numeraljs.com/#format[depends on the value], typically MB or GB + +| uptime | process.uptime_in_millis | HH:mm:ss + +| load | os.load | [ "load for the last 1 min" "load for the last 5 min" "load for the last 15 min"] + +| delay | process.event_loop_delay | ms +|=== + +The log interval is the same as the interval at which system and process information is refreshed and is configurable under `ops.interval`: + +[source,yaml] +---- +ops.interval: 5000 +---- + +The minimum interval is 100ms and defaults to 5000ms. + +[[request-response-logger]] +**Request and Response Logs** + +The `http.server.response` logger is configured with `debug` level and will automatically output +data about http requests and responses occurring on the {kib} server. +The message contains some high-level information, and the corresponding log meta contains the following: + +[options="header"] +|=== + +| Meta property | Description | Format + +| client.ip | IP address of the requesting client | ip + +| http.request.method | http verb for the request (uppercase) | string + +| http.request.mime_type | (optional) mime as specified in the headers | string + +| http.request.referrer | (optional) referrer | string + +| http.request.headers | request headers | object + +| http.response.body.bytes | (optional) Calculated response payload size in bytes | number + +| http.response.status_code | status code returned | number + +| http.response.headers | response headers | object + +| http.response.responseTime | (optional) Calculated response time in ms | number + +| url.path | request path | string + +| url.query | (optional) request query string | string + +| user_agent.original | raw user-agent string provided in request headers | string + +|=== + +=== Usage + +Usage is very straightforward, one should just get a logger for a specific context and use it to log messages with +different log level. + +[source,typescript] +---- +const logger = kibana.logger.get('server'); + +logger.trace('Message with `trace` log level.'); +logger.debug('Message with `debug` log level.'); +logger.info('Message with `info` log level.'); +logger.warn('Message with `warn` log level.'); +logger.error('Message with `error` log level.'); +logger.fatal('Message with `fatal` log level.'); + +const loggerWithNestedContext = kibana.logger.get('server', 'http'); +loggerWithNestedContext.trace('Message with `trace` log level.'); +loggerWithNestedContext.debug('Message with `debug` log level.'); +---- + +And assuming logger for `server` name with `console` appender and `trace` level was used, console output will look like this: +[source,bash] +---- +[2017-07-25T11:54:41.639-07:00][TRACE][server] Message with `trace` log level. +[2017-07-25T11:54:41.639-07:00][DEBUG][server] Message with `debug` log level. +[2017-07-25T11:54:41.639-07:00][INFO ][server] Message with `info` log level. +[2017-07-25T11:54:41.639-07:00][WARN ][server] Message with `warn` log level. +[2017-07-25T11:54:41.639-07:00][ERROR][server] Message with `error` log level. +[2017-07-25T11:54:41.639-07:00][FATAL][server] Message with `fatal` log level. + +[2017-07-25T11:54:41.639-07:00][TRACE][server.http] Message with `trace` log level. +[2017-07-25T11:54:41.639-07:00][DEBUG][server.http] Message with `debug` log level. +---- + +The log will be less verbose with `warn` level for the `server` logger: +[source,bash] +---- +[2017-07-25T11:54:41.639-07:00][WARN ][server] Message with `warn` log level. +[2017-07-25T11:54:41.639-07:00][ERROR][server] Message with `error` log level. +[2017-07-25T11:54:41.639-07:00][FATAL][server] Message with `fatal` log level. +---- diff --git a/docs/developer/architecture/core/patterns-scoped-services.asciidoc b/docs/developer/architecture/core/patterns-scoped-services.asciidoc new file mode 100644 index 00000000000000..d4618684fc7e4b --- /dev/null +++ b/docs/developer/architecture/core/patterns-scoped-services.asciidoc @@ -0,0 +1,61 @@ +[[patterns]] +== Patterns +[[scoped-services]] +=== Scoped services +Whenever Kibana needs to get access to data saved in Elasticsearch, it +should perform a check whether an end-user has access to the data. +The Kibana Platform introduced a handler interface on the server-side to perform that association +internally. Core services, that require impersonation with an incoming +request, are exposed via `context` argument of +{kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.requesthandler.md[the +request handler interface.] +as + +[source,js] +---- +async function handler(context, req, res) { + const data = await context.core.elasticsearch.client.asCurrentUser('ping'); +} +---- + +The +{kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.requesthandlercontext.md[request +handler context] exposes the following scoped *core* services: + +* {kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.savedobjectsclient.md[`context.savedObjects.client`] +* {kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.iscopedclusterclient.md[`context.elasticsearch.client`] +* {kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.iuisettingsclient.md[`context.uiSettings.client`] + +==== Declare a custom scoped service + +Plugins can extend the handler context with a custom API that will be +available to the plugin itself and all dependent plugins. For example, +the plugin creates a custom Elasticsearch client and wants to use it via +the request handler context: + +[source,typescript] +---- +import type { CoreSetup, RequestHandlerContext, IScopedClusterClient } from 'kibana/server'; + +interface MyRequestHandlerContext extends RequestHandlerContext { + myPlugin: { + client: IScopedClusterClient; + }; +} + +class MyPlugin { + setup(core: CoreSetup) { + const client = core.elasticsearch.createClient('myClient'); + core.http.registerRouteHandlerContext('myPlugin', (context, req, res) => { + return { client: client.asScoped(req) }; + }); + const router = core.http.createRouter(); + router.get( + { path: '/api/my-plugin/', validate: … }, + async (context, req, res) => { + // context type is inferred as MyPluginContext + const data = await context.myPlugin.client.asCurrentUser('endpoint'); + } + ); + } +---- diff --git a/docs/developer/architecture/core/saved-objects-service.asciidoc b/docs/developer/architecture/core/saved-objects-service.asciidoc index 047c3dffa63583..fa7fc4233259d0 100644 --- a/docs/developer/architecture/core/saved-objects-service.asciidoc +++ b/docs/developer/architecture/core/saved-objects-service.asciidoc @@ -1,6 +1,8 @@ [[saved-objects-service]] == Saved Objects service +NOTE: The Saved Objects service is available both server and client side. + `Saved Objects service` allows {kib} plugins to use {es} like a primary database. Think of it as an Object Document Mapper for {es}. Once a plugin has registered one or more Saved Object types, the Saved Objects client @@ -28,7 +30,9 @@ spaces. This document contains developer guidelines and best-practices for plugins wanting to use Saved Objects. -=== Registering a Saved Object type +=== Server side usage + +==== Registering a Saved Object type Saved object type definitions should be defined in their own `my_plugin/server/saved_objects` directory. The folder should contain a file per type, named after the snake_case name of the type, and an `index.ts` file exporting all the types. @@ -83,7 +87,7 @@ export class MyPlugin implements Plugin { } ---- -=== Mappings +==== Mappings Each Saved Object type can define it's own {es} field mappings. Because multiple Saved Object types can share the same index, mappings defined by a type will be nested under a top-level field that matches the type name. @@ -149,59 +153,6 @@ should carefully consider the fields they add to the mappings. Similarly, Saved Object types should never use `dynamic: true` as this can cause an arbitrary amount of fields to be added to the `.kibana` index. -=== References -When a Saved Object declares `references` to other Saved Objects, the -Saved Objects Export API will automatically export the target object with all -of it's references. This makes it easy for users to export the entire -reference graph of an object. - -If a Saved Object can't be used on it's own, that is, it needs other objects -to exist for a feature to function correctly, that Saved Object should declare -references to all the objects it requires. For example, a `dashboard` -object might have panels for several `visualization` objects. When these -`visualization` objects don't exist, the dashboard cannot be rendered -correctly. The `dashboard` object should declare references to all it's -visualizations. - -However, `visualization` objects can continue to be rendered or embedded into -other dashboards even if the `dashboard` it was originally embedded into -doesn't exist. As a result, `visualization` objects should not declare -references to `dashboard` objects. - -For each referenced object, an `id`, `type` and `name` are added to the -`references` array: - -[source, typescript] ----- -router.get( - { path: '/some-path', validate: false }, - async (context, req, res) => { - const object = await context.core.savedObjects.client.create( - 'dashboard', - { - title: 'my dashboard', - panels: [ - { visualization: 'vis1' }, // <1> - ], - indexPattern: 'indexPattern1' - }, - { references: [ - { id: '...', type: 'visualization', name: 'vis1' }, - { id: '...', type: 'index_pattern', name: 'indexPattern1' }, - ] - } - ) - ... - } -); ----- -<1> Note how `dashboard.panels[0].visualization` stores the `name` property of -the reference (not the `id` directly) to be able to uniquely identify this -reference. This guarantees that the id the reference points to always remains -up to date. If a visualization `id` was directly stored in -`dashboard.panels[0].visualization` there is a risk that this `id` gets -updated without updating the reference in the references array. - ==== Writing Migrations Saved Objects support schema changes between Kibana versions, which we call @@ -308,4 +259,60 @@ point in time. It is critical that you have extensive tests to ensure that migrations behave as expected with all possible input documents. Given how simple it is to test all the branch conditions in a migration function and the high impact of a bug -in this code, there's really no reason not to aim for 100% test code coverage. \ No newline at end of file +in this code, there's really no reason not to aim for 100% test code coverage. + +=== Client side usage + +==== References + +When a Saved Object declares `references` to other Saved Objects, the +Saved Objects Export API will automatically export the target object with all +of its references. This makes it easy for users to export the entire +reference graph of an object. + +If a Saved Object can't be used on its own, that is, it needs other objects +to exist for a feature to function correctly, that Saved Object should declare +references to all the objects it requires. For example, a `dashboard` +object might have panels for several `visualization` objects. When these +`visualization` objects don't exist, the dashboard cannot be rendered +correctly. The `dashboard` object should declare references to all its +visualizations. + +However, `visualization` objects can continue to be rendered or embedded into +other dashboards even if the `dashboard` it was originally embedded into +doesn't exist. As a result, `visualization` objects should not declare +references to `dashboard` objects. + +For each referenced object, an `id`, `type` and `name` are added to the +`references` array: + +[source, typescript] +---- +router.get( + { path: '/some-path', validate: false }, + async (context, req, res) => { + const object = await context.core.savedObjects.client.create( + 'dashboard', + { + title: 'my dashboard', + panels: [ + { visualization: 'vis1' }, // <1> + ], + indexPattern: 'indexPattern1' + }, + { references: [ + { id: '...', type: 'visualization', name: 'vis1' }, + { id: '...', type: 'index_pattern', name: 'indexPattern1' }, + ] + } + ) + ... + } +); +---- +<1> Note how `dashboard.panels[0].visualization` stores the `name` property of +the reference (not the `id` directly) to be able to uniquely identify this +reference. This guarantees that the id the reference points to always remains +up to date. If a visualization `id` was directly stored in +`dashboard.panels[0].visualization` there is a risk that this `id` gets +updated without updating the reference in the references array. diff --git a/docs/developer/architecture/core/uisettings-service.asciidoc b/docs/developer/architecture/core/uisettings-service.asciidoc new file mode 100644 index 00000000000000..85ed9c9eabc726 --- /dev/null +++ b/docs/developer/architecture/core/uisettings-service.asciidoc @@ -0,0 +1,40 @@ +[[ui-settings-service]] +== UI settings service + +NOTE: The UI settings service is available both server and client side. + +=== Server side usage + +The program interface to <>. +It makes it possible for Kibana plugins to extend Kibana UI Settings Management with custom settings. + +See: + +- {kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.uisettingsservicesetup.register.md[UI settings service Setup API docs] + +[source,typescript] +---- +import { schema } from '@kbn/config-schema'; +import type { CoreSetup,Plugin } from 'kibana/server'; + +export class MyPlugin implements Plugin { + public setup(core: CoreSetup) { + core.uiSettings.register({ + custom: { + value: '42', + schema: schema.string(), + }, + }); + const router = core.http.createRouter(); + router.get({ + path: 'my_plugin/{id}', + validate: …, + }, + async (context, request, response) => { + const customSetting = await context.uiSettings.client.get('custom'); + … + }); + } +} + +---- diff --git a/docs/developer/architecture/index.asciidoc b/docs/developer/architecture/index.asciidoc index 4bdd693979b491..1a0e7bab2f8f8f 100644 --- a/docs/developer/architecture/index.asciidoc +++ b/docs/developer/architecture/index.asciidoc @@ -29,6 +29,24 @@ include::kibana-platform-plugin-api.asciidoc[leveloffset=+1] include::core/index.asciidoc[leveloffset=+1] +include::core/application_service.asciidoc[leveloffset=+1] + +include::core/configuration-service.asciidoc[leveloffset=+1] + +include::core/elasticsearch-service.asciidoc[leveloffset=+1] + +include::core/http-service.asciidoc[leveloffset=+1] + +include::core/logging-service.asciidoc[leveloffset=+1] + +include::core/logging-configuration-migration.asciidoc[leveloffset=+1] + +include::core/saved-objects-service.asciidoc[leveloffset=+1] + +include::core/uisettings-service.asciidoc[leveloffset=+1] + +include::core/patterns-scoped-services.asciidoc[leveloffset=+1] + include::security/index.asciidoc[leveloffset=+1] include::add-data-tutorials.asciidoc[leveloffset=+1] diff --git a/docs/migration/migrate_8_0.asciidoc b/docs/migration/migrate_8_0.asciidoc index 52d1d63ce0653b..f5ebac1ebf02e6 100644 --- a/docs/migration/migrate_8_0.asciidoc +++ b/docs/migration/migrate_8_0.asciidoc @@ -50,46 +50,56 @@ for example, `logstash-*`. [float] ==== Default logging timezone is now the system's timezone -*Details:* In prior releases the timezone used in logs defaulted to UTC. We now use the host machine's timezone by default. +*Details:* In prior releases the timezone used in logs defaulted to UTC. We now use the host machine's timezone by default. *Impact:* To restore the previous behavior, in kibana.yml use the pattern layout, with a date modifier: [source,yaml] ------------------- logging: appenders: - console: - kind: console + custom: + type: console layout: - kind: pattern + type: pattern pattern: "%date{ISO8601_TZ}{UTC}" ------------------- See https://github.com/elastic/kibana/pull/90368 for more details. [float] ==== Responses are never logged by default -*Details:* Previously responses would be logged if either `logging.json` was true, `logging.dest` was specified, or a `TTY` was detected. +*Details:* Previously responses would be logged if either `logging.json` was true, `logging.dest` was specified, or a `TTY` was detected. With the new logging configuration, these are provided by a dedicated logger. -*Impact:* To restore the previous behavior, in kibana.yml enable `debug` logs for the `http.server.response` context under `logging.loggers`: +*Impact:* To restore the previous behavior, in `kibana.yml` enable `debug` for the `http.server.response` logger: [source,yaml] ------------------- logging: + appenders: + custom: + type: console + layout: + type: pattern loggers: - - context: http.server.response - appenders: [console] + - name: http.server.response + appenders: [custom] level: debug ------------------- See https://github.com/elastic/kibana/pull/87939 for more details. [float] ==== Logging destination is specified by the appender -*Details:* Previously log destination would be `stdout` and could be changed to `file` using `logging.dest`. +*Details:* Previously log destination would be `stdout` and could be changed to `file` using `logging.dest`. With the new logging configuration, you can specify the destination using appenders. -*Impact:* To restore the previous behavior, in `kibana.yml` use the `console` appender to send logs to `stdout`. +*Impact:* To restore the previous behavior and log records to *stdout*, in `kibana.yml` use an appender with `type: console`. [source,yaml] ------------------- logging: + appenders: + custom: + type: console + layout: + type: pattern root: - appenders: [default, console] + appenders: [default, custom] ------------------- To send logs to `file` with a given file path, you should define a custom appender with `type:file`: @@ -107,16 +117,15 @@ logging: ------------------- [float] -==== Specify log event output with root -*Details:* Previously logging output would be specified by `logging.silent` (none), 'logging.quiet' (error messages only) and `logging.verbose` (all). +==== Set log verbosity with root +*Details:* Previously logging output would be specified by `logging.silent` (none), `logging.quiet` (error messages only) and `logging.verbose` (all). With the new logging configuration, set the minimum required log level. -*Impact:* To restore the previous behavior, in `kibana.yml` specify `logging.root.level` as one of `off`, `error`, `all`: +*Impact:* To restore the previous behavior, in `kibana.yml` specify `logging.root.level`: [source,yaml] ------------------- # suppress all logs logging: root: - appenders: [default] level: off ------------------- @@ -125,7 +134,6 @@ logging: # only log error messages logging: root: - appenders: [default] level: error ------------------- @@ -134,54 +142,14 @@ logging: # log all events logging: root: - appenders: [default] - level: all -------------------- - -[float] -==== Suppress all log output with root -*Details:* Previously all logging output would be suppressed if `logging.silent` was true. - -*Impact:* To restore the previous behavior, in `kibana.yml` turn `logging.root.level` to 'off'. -[source,yaml] -------------------- -logging: - root: - appenders: [default] - level: off -------------------- - -[float] -==== Suppress log output with root -*Details:* Previously all logging output other than error messages would be suppressed if `logging.quiet` was true. - -*Impact:* To restore the previous behavior, in `kibana.yml` turn `logging.root.level` to 'error'. -[source,yaml] -------------------- -logging: - root: - appenders: [default] - level: error -------------------- - -[float] -==== Log all output with root -*Details:* Previously all events would be logged if `logging.verbose` was true. - -*Impact:* To restore the previous behavior, in `kibana.yml` turn `logging.root.level` to 'all'. -[source,yaml] -------------------- -logging: - root: - appenders: [default] level: all ------------------- [float] -==== Declare log message format for each custom appender -*Details:* Previously all events would be logged in `json` format when `logging.json` was true. +==== Declare log message format +*Details:* Previously all events would be logged in `json` format when `logging.json` was true. With the new logging configuration you can specify the output format with layouts. You can choose between `json` and pattern format depending on your needs. -*Impact:* To restore the previous behavior, in `kibana.yml` configure the logging format for each custom appender with the `appender.layout` property. There is no default for custom appenders and each one must be configured expilictly. +*Impact:* To restore the previous behavior, in `kibana.yml` configure the logging format for each custom appender with the `appender.layout` property. There is no default for custom appenders and each one must be configured expilictly. [source,yaml] ------------------- diff --git a/docs/settings/logging-settings.asciidoc b/docs/settings/logging-settings.asciidoc new file mode 100644 index 00000000000000..aa38d54305eec8 --- /dev/null +++ b/docs/settings/logging-settings.asciidoc @@ -0,0 +1,173 @@ +[[logging-settings]] +=== Logging settings in {kib} +++++ +Logging settings +++++ + +Compatibility with the legacy logging system is assured until the end of the `v7` version. +All log messages handled by `root` context (default) are forwarded to the legacy logging service. +The logging configuration is validated against the predefined schema and if there are +any issues with it, {kib} will fail to start with the detailed error message. + +NOTE: When you switch to the new logging configuration, you will start seeing duplicate log entries in both formats. +These will be removed when the `default` appender is no longer required. + +Here are some configuration examples for the most common logging use cases: + +[[log-to-file-example]] +==== Log to a file + +Log the default log format to a file instead of to stdout (the default). + +[source,yaml] +---- +logging: + appenders: + file: + type: file + fileName: /var/log/kibana.log + layout: + type: pattern + root: + appenders: [default, file] +---- + +[[log-in-json-ECS-example]] +==== Log in json format + +Log the default log format to json layout instead of pattern (the default). +With `json` layout log messages will be formatted as JSON strings in https://www.elastic.co/guide/en/ecs/current/ecs-reference.html[ECS format] that includes a timestamp, log level, logger, message text and any other metadata that may be associated with the log message itself + +[source,yaml] +---- +logging: + appenders: + json-layout: + type: console + layout: + type: json + root: + appenders: [default, json-layout] +---- + +[[log-with-meta-to-stdout]] +==== Log with meta to stdout + +Include `%meta` in your pattern layout: + +[source,yaml] +---- +logging: + appenders: + console-meta: + type: console + layout: + type: pattern + pattern: "[%date] [%level] [%logger] [%meta] %message" + root: + appenders: [default, console-meta] +---- + +[[log-elasticsearch-queries]] +==== Log {es} queries + +[source,yaml] +-- +logging: + appenders: + console_appender: + type: console + layout: + type: pattern + highlight: true + root: + appenders: [default, console_appender] + level: warn + loggers: + - name: elasticsearch.query + level: debug +-- + +[[change-overall-log-level]] +==== Change overall log level. + +[source,yaml] +---- +logging: + root: + level: debug +---- + +[[customize-specific-log-records]] +==== Customize specific log records +Here is a detailed configuration example that can be used to configure _loggers_, _appenders_ and _layouts_: + +[source,yaml] +---- +logging: + appenders: + console: + type: console + layout: + type: pattern + highlight: true + file: + type: file + fileName: /var/log/kibana.log + custom: + type: console + layout: + type: pattern + pattern: "[%date][%level] %message" + json-file-appender: + type: file + fileName: /var/log/kibana-json.log + layout: + type: json + + root: + appenders: [default, console, file] + level: error + + loggers: + - name: plugins + appenders: [custom] + level: warn + - name: plugins.myPlugin + level: info + - name: server + level: fatal + - name: optimize + appenders: [console] + - name: telemetry + appenders: [json-file-appender] + level: all + - name: metrics.ops + appenders: [console] + level: debug +---- + +Here is what we get with the config above: +[options="header"] +|=== + +| Context name | Appenders | Level + +| root | console, file | error + +| plugins | custom | warn + +| plugins.myPlugin | custom | info + +| server | console, file | fatal + +| optimize | console | error + +| telemetry | json-file-appender | all + +| metrics.ops | console | debug +|=== + +NOTE: If you modify `root.appenders`, make sure to include `default`. + +// For more details about logging configuration, refer to the logging system documentation (update to include a link). diff --git a/docs/setup/settings.asciidoc b/docs/setup/settings.asciidoc index 62e0f0847cbac6..e5cbc2c7ea6db7 100644 --- a/docs/setup/settings.asciidoc +++ b/docs/setup/settings.asciidoc @@ -64,11 +64,34 @@ To enable SSL/TLS for outbound connections to {es}, use the `https` protocol in this setting. | `elasticsearch.logQueries:` - | *deprecated* This setting is no longer used and will get removed in Kibana 8.0. Instead, set <> to `true` + | *deprecated* This setting is no longer used and will get removed in Kibana 8.0. Instead, configure the `elasticsearch.query` logger. This is useful for seeing the query DSL generated by applications that currently do not have an inspector, for example Timelion and Monitoring. *Default: `false`* +The following example shows a valid `elasticsearch.query` logger configuration: +|=== + +[source,text] +-- +logging: + appenders: + console_appender: + type: console + layout: + type: pattern + highlight: true + root: + appenders: [default, console_appender] + level: warn + loggers: + - name: elasticsearch.query + level: debug +-- + +[cols="2*<"] +|=== + |[[elasticsearch-pingTimeout]] `elasticsearch.pingTimeout:` | Time in milliseconds to wait for {es} to respond to pings. *Default: the value of the <> setting* @@ -249,77 +272,44 @@ To reload the logging settings, send a SIGHUP signal to {kib}. [cols="2*<"] |=== -|[[logging-dest]] `logging.dest:` - | Enables you to specify a file where {kib} stores log output. -*Default: `stdout`* +|[[logging-root]] `logging.root:` +| The `root` logger has a dedicated configuration node since this context name is special and is pre-configured for logging by default. +// TODO: add link to the advanced logging documentation. -| `logging.json:` - | Logs output as JSON. When set to `true`, the logs are formatted as JSON -strings that include timestamp, log level, context, message text, and any other -metadata that may be associated with the log message. -When <> is set, and there is no interactive terminal ("TTY"), -this setting defaults to `true`. *Default: `false`* +|[[logging-root-appenders]] `logging.root.appenders:` +| A list of logging appenders to forward the root level logger instance to. By default `root` is configured with the `default` appender that must be included in the list. This is the configuration that all custom loggers will use unless they're re-configured explicitly. Additional appenders, if configured, can be included in the list. -| `logging.quiet:` - | Set the value of this setting to `true` to suppress all logging output other -than error messages. *Default: `false`* +|[[logging-root-level]] `logging.root.level:` {ess-icon} +| Level at which a log record should be logged. Supported levels are: _all_, _fatal_, _error_, _warn_, _info_, _debug_, _trace_, _off_. Levels are ordered from _all_ (highest) to _off_ and a log record will be logged it its level is higher than or equal to the level of its logger, otherwise the log record is ignored. Use this value to <>. Set to `all` to log all events, including system usage information and all requests. Set to `off` to silence all logs. *Default: `info`*. -| `logging.rotate:` - | experimental[] Specifies the options for the logging rotate feature. -When not defined, all the sub options defaults would be applied. -The following example shows a valid logging rotate configuration: +|[[logging-loggers]] `logging.loggers:` + | Allows you to <>. -|=== +| `logging.loggers.name:` +| Specific logger instance. -[source,text] --- - logging.rotate: - enabled: true - everyBytes: 10485760 - keepFiles: 10 --- +| `logging.loggers.level:` +| Level at which a log record should be shown. Supported levels are: _all_, _fatal_, _error_, _warn_, _info_, _debug_, _trace_, _off_. -[cols="2*<"] -|=== +| `logging.loggers.appenders:` +| Specific appender format to apply for a particular logger context. + +| `logging.appenders:` +| Define how and where log messages are displayed (eg. *stdout* or console) and stored (eg. file on the disk). +// TODO: add link to the advanced logging documentation. + +| `logging.appenders.console:` +| Appender to use for logging records to *stdout*. By default, uses the `[%date][%level][%logger] %message` **pattern** layout. To use a **json**, set the <>. + +| `logging.appenders.file:` +| Allows you to specify a fileName to send log records to on disk. To send <>, add the file appender to `root.appenders`. + +| `logging.appenders.rolling-file:` +| Similar to Log4j's `RollingFileAppender`, this appender will log into a file and rotate if following a rolling strategy when the configured policy triggers. There are currently two policies supported: `size-limit` and `time-interval`. + +The size limit policy will perform a rollover when the log file reaches a maximum `size`. *Default 100mb* -| `logging.rotate.enabled:` - | experimental[] Set the value of this setting to `true` to -enable log rotation. If you do not have a <> set that is different from `stdout` -that feature would not take any effect. *Default: `false`* - -| `logging.rotate.everyBytes:` - | experimental[] The maximum size of a log file (that is `not an exact` limit). After the -limit is reached, a new log file is generated. The default size limit is 10485760 (10 MB) and -this option should be in the range of 1048576 (1 MB) to 1073741824 (1 GB). *Default: `10485760`* - -| `logging.rotate.keepFiles:` - | experimental[] The number of most recent rotated log files to keep -on disk. Older files are deleted during log rotation. The default value is 7. The `logging.rotate.keepFiles` -option has to be in the range of 2 to 1024 files. *Default: `7`* - -| `logging.rotate.pollingInterval:` - | experimental[] The number of milliseconds for the polling strategy in case -the <> is enabled. `logging.rotate.usePolling` must be in the 5000 to 3600000 millisecond range. *Default: `10000`* - -|[[logging-rotate-usePolling]] `logging.rotate.usePolling:` - | experimental[] By default we try to understand the best way to monitoring -the log file and warning about it. Please be aware there are some systems where watch api is not accurate. In those cases, in order to get the feature working, -the `polling` method could be used enabling that option. *Default: `false`* - -| `logging.silent:` - | Set the value of this setting to `true` to -suppress all logging output. *Default: `false`* - -| `logging.timezone` - | Set to the canonical time zone ID -(for example, `America/Los_Angeles`) to log events using that time zone. -For possible values, refer to -https://en.wikipedia.org/wiki/List_of_tz_database_time_zones[database time zones]. -When not set, log events use the host timezone - -| [[logging-verbose]] `logging.verbose:` {ess-icon} - | Set to `true` to log all events, including system usage information and all -requests. *Default: `false`* +The time interval policy will rotate the log file every given interval of time. *Default 24h* | [[regionmap-ES-map]] `map.includeElasticMapsService:` {ess-icon} | Set to `false` to disable connections to Elastic Maps Service. @@ -690,6 +680,7 @@ include::{kib-repo-dir}/settings/dev-settings.asciidoc[] include::{kib-repo-dir}/settings/graph-settings.asciidoc[] include::{kib-repo-dir}/settings/fleet-settings.asciidoc[] include::{kib-repo-dir}/settings/i18n-settings.asciidoc[] +include::{kib-repo-dir}/settings/logging-settings.asciidoc[] include::{kib-repo-dir}/settings/logs-ui-settings.asciidoc[] include::{kib-repo-dir}/settings/infrastructure-ui-settings.asciidoc[] include::{kib-repo-dir}/settings/ml-settings.asciidoc[] diff --git a/docs/user/reporting/reporting-troubleshooting.asciidoc b/docs/user/reporting/reporting-troubleshooting.asciidoc index ebe095e0881b38..c43e9210dd7c80 100644 --- a/docs/user/reporting/reporting-troubleshooting.asciidoc +++ b/docs/user/reporting/reporting-troubleshooting.asciidoc @@ -126,10 +126,10 @@ all, the full logs from Reporting will be the first place to look. In `kibana.ym [source,yaml] -------------------------------------------------------------------------------- -logging.verbose: true +logging.root.level: all -------------------------------------------------------------------------------- -For more information about logging, see <>. +For more information about logging, see <>. [float] [[reporting-troubleshooting-puppeteer-debug-logs]] From 1bfa719d29a68ef65ba9037ccdb266b03f173b72 Mon Sep 17 00:00:00 2001 From: Aleksandr Maus Date: Tue, 30 Mar 2021 11:40:07 -0400 Subject: [PATCH 13/32] Disable indexing for .fleet-agents action_seq_no field (#95584) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../services/fleet_server/elasticsearch/fleet_agents.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/fleet/server/services/fleet_server/elasticsearch/fleet_agents.json b/x-pack/plugins/fleet/server/services/fleet_server/elasticsearch/fleet_agents.json index 58ae1a2e00ea49..32caa684679d8c 100644 --- a/x-pack/plugins/fleet/server/services/fleet_server/elasticsearch/fleet_agents.json +++ b/x-pack/plugins/fleet/server/services/fleet_server/elasticsearch/fleet_agents.json @@ -7,7 +7,8 @@ "type": "keyword" }, "action_seq_no": { - "type": "integer" + "type": "integer", + "index": false }, "active": { "type": "boolean" From c8bc57257b87caed19612ce8b3ec535edccf03fc Mon Sep 17 00:00:00 2001 From: Paul Tavares <56442535+paul-tavares@users.noreply.github.com> Date: Tue, 30 Mar 2021 11:50:54 -0400 Subject: [PATCH 14/32] [FLEET][Artifacts] Use artifact information to generate a unique ID when creating a new Artifact (#95705) * Change id generation for artifact create --- x-pack/plugins/fleet/server/errors/utils.ts | 4 ++++ .../services/artifacts/artifacts.test.ts | 2 +- .../server/services/artifacts/artifacts.ts | 20 +++++++++++++------ .../server/services/artifacts/mappings.ts | 10 ++++++++++ 4 files changed, 29 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/fleet/server/errors/utils.ts b/x-pack/plugins/fleet/server/errors/utils.ts index 6d7d4aaffa2a30..2eae04e05bd6bc 100644 --- a/x-pack/plugins/fleet/server/errors/utils.ts +++ b/x-pack/plugins/fleet/server/errors/utils.ts @@ -10,3 +10,7 @@ import { ResponseError } from '@elastic/elasticsearch/lib/errors'; export function isESClientError(error: unknown): error is ResponseError { return error instanceof ResponseError; } + +export const isElasticsearchVersionConflictError = (error: Error): boolean => { + return isESClientError(error) && error.meta.statusCode === 409; +}; diff --git a/x-pack/plugins/fleet/server/services/artifacts/artifacts.test.ts b/x-pack/plugins/fleet/server/services/artifacts/artifacts.test.ts index 07232e66b4467e..20e80a2f997afc 100644 --- a/x-pack/plugins/fleet/server/services/artifacts/artifacts.test.ts +++ b/x-pack/plugins/fleet/server/services/artifacts/artifacts.test.ts @@ -85,7 +85,7 @@ describe('When using the artifacts services', () => { expect(esClientMock.create).toHaveBeenCalledWith({ index: FLEET_SERVER_ARTIFACTS_INDEX, - id: expect.any(String), + id: `${artifact.packageName}:${artifact.identifier}-${artifact.decodedSha256}`, body: { ...newArtifactToElasticsearchProperties(newArtifact), created: expect.any(String), diff --git a/x-pack/plugins/fleet/server/services/artifacts/artifacts.ts b/x-pack/plugins/fleet/server/services/artifacts/artifacts.ts index 77785aeb026c12..6e2c22cc2f0456 100644 --- a/x-pack/plugins/fleet/server/services/artifacts/artifacts.ts +++ b/x-pack/plugins/fleet/server/services/artifacts/artifacts.ts @@ -11,7 +11,6 @@ import { promisify } from 'util'; import type { BinaryLike } from 'crypto'; import { createHash } from 'crypto'; -import uuid from 'uuid'; import type { ElasticsearchClient } from 'kibana/server'; import type { ListResult } from '../../../common'; @@ -19,6 +18,8 @@ import { FLEET_SERVER_ARTIFACTS_INDEX } from '../../../common'; import { ArtifactsElasticsearchError } from '../../errors'; +import { isElasticsearchVersionConflictError } from '../../errors/utils'; + import { isElasticsearchItemNotFoundError } from './utils'; import type { Artifact, @@ -28,7 +29,11 @@ import type { ListArtifactsProps, NewArtifact, } from './types'; -import { esSearchHitToArtifact, newArtifactToElasticsearchProperties } from './mappings'; +import { + esSearchHitToArtifact, + newArtifactToElasticsearchProperties, + uniqueIdFromArtifact, +} from './mappings'; const deflateAsync = promisify(deflate); @@ -57,7 +62,7 @@ export const createArtifact = async ( esClient: ElasticsearchClient, artifact: NewArtifact ): Promise => { - const id = uuid.v4(); + const id = uniqueIdFromArtifact(artifact); const newArtifactData = newArtifactToElasticsearchProperties(artifact); try { @@ -67,11 +72,14 @@ export const createArtifact = async ( body: newArtifactData, refresh: 'wait_for', }); - - return esSearchHitToArtifact({ _id: id, _source: newArtifactData }); } catch (e) { - throw new ArtifactsElasticsearchError(e); + // we ignore 409 errors from the create (document already exists) + if (!isElasticsearchVersionConflictError(e)) { + throw new ArtifactsElasticsearchError(e); + } } + + return esSearchHitToArtifact({ _id: id, _source: newArtifactData }); }; export const deleteArtifact = async (esClient: ElasticsearchClient, id: string): Promise => { diff --git a/x-pack/plugins/fleet/server/services/artifacts/mappings.ts b/x-pack/plugins/fleet/server/services/artifacts/mappings.ts index 3b81e47577ff7f..79d14a27fa8271 100644 --- a/x-pack/plugins/fleet/server/services/artifacts/mappings.ts +++ b/x-pack/plugins/fleet/server/services/artifacts/mappings.ts @@ -72,3 +72,13 @@ export const relativeDownloadUrlFromArtifact = < decodedSha256 ); }; + +export const uniqueIdFromArtifact = < + T extends Pick +>({ + identifier, + decodedSha256, + packageName, +}: T): string => { + return `${packageName}:${identifier}-${decodedSha256}`; +}; From a154d92d6f77e2ec80f81be18f50c0f86094b241 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Tue, 30 Mar 2021 17:51:52 +0200 Subject: [PATCH 15/32] update mock and types (#95771) --- x-pack/plugins/lens/public/index.ts | 2 ++ x-pack/plugins/lens/public/{mocks.ts => mocks.tsx} | 8 ++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) rename x-pack/plugins/lens/public/{mocks.ts => mocks.tsx} (68%) diff --git a/x-pack/plugins/lens/public/index.ts b/x-pack/plugins/lens/public/index.ts index fa5a9f9289e92a..9b53e59f96792b 100644 --- a/x-pack/plugins/lens/public/index.ts +++ b/x-pack/plugins/lens/public/index.ts @@ -19,7 +19,9 @@ export type { SeriesType, ValueLabelConfig, YAxisMode, + XYCurveType, } from './xy_visualization/types'; +export type { DataType } from './types'; export type { PieVisualizationState, PieLayerState, diff --git a/x-pack/plugins/lens/public/mocks.ts b/x-pack/plugins/lens/public/mocks.tsx similarity index 68% rename from x-pack/plugins/lens/public/mocks.ts rename to x-pack/plugins/lens/public/mocks.tsx index fd1e38db242a83..743846d81213c0 100644 --- a/x-pack/plugins/lens/public/mocks.ts +++ b/x-pack/plugins/lens/public/mocks.tsx @@ -5,16 +5,20 @@ * 2.0. */ +import React from 'react'; import { LensPublicStart } from '.'; +import { visualizationTypes } from './xy_visualization/types'; export type Start = jest.Mocked; const createStartContract = (): Start => { const startContract: Start = { - EmbeddableComponent: jest.fn(() => null), + EmbeddableComponent: jest.fn(() => { + return Lens Embeddable Component; + }), canUseEditor: jest.fn(() => true), navigateToPrefilledEditor: jest.fn(), - getXyVisTypes: jest.fn(), + getXyVisTypes: jest.fn().mockReturnValue(new Promise(() => visualizationTypes)), }; return startContract; }; From ceb4c9a1609c2e7d206d778e522bb886611a6962 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Tue, 30 Mar 2021 18:01:41 +0200 Subject: [PATCH 16/32] do not re-render embeddable so often (#95653) --- .../embeddable/embeddable.test.tsx | 15 ++++++++++++++- .../embeddable/embeddable.tsx | 5 ++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.test.tsx index efe16c30935bea..157975b630e1e5 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.test.tsx @@ -338,7 +338,13 @@ describe('embeddable', () => { expect(expressionRenderer).toHaveBeenCalledTimes(2); }); - it('should re-render when dashboard view/edit mode changes', async () => { + it('should re-render when dashboard view/edit mode changes if dynamic actions are set', async () => { + const sampleInput = ({ + id: '123', + enhancements: { + dynamicActions: {}, + }, + } as unknown) as LensEmbeddableInput; const embeddable = new Embeddable( { timefilter: dataPluginMock.createSetupContract().query.timefilter.timefilter, @@ -371,6 +377,13 @@ describe('embeddable', () => { viewMode: ViewMode.VIEW, }); + expect(expressionRenderer).toHaveBeenCalledTimes(1); + + embeddable.updateInput({ + ...sampleInput, + viewMode: ViewMode.VIEW, + }); + expect(expressionRenderer).toHaveBeenCalledTimes(2); }); diff --git a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx index b395352b614774..1db067606dc82f 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx @@ -172,7 +172,10 @@ export class Embeddable skip(1) ) .subscribe((input) => { - this.reload(); + // only reload if drilldowns are set + if (this.getInput().enhancements?.dynamicActions) { + this.reload(); + } }) ); From a67aa97989f8d40aa73e1ae6f31aabf60da3dc2f Mon Sep 17 00:00:00 2001 From: Ahmad Bamieh Date: Tue, 30 Mar 2021 19:20:23 +0300 Subject: [PATCH 17/32] [Core] create deprecations service (#94845) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- ...ugin-core-public.corestart.deprecations.md | 13 + .../kibana-plugin-core-public.corestart.md | 1 + ...ecationsservicestart.getalldeprecations.md | 13 + ...eprecationsservicestart.getdeprecations.md | 13 + ...onsservicestart.isdeprecationresolvable.md | 13 + ...in-core-public.deprecationsservicestart.md | 23 ++ ...ecationsservicestart.resolvedeprecation.md | 13 + .../core/public/kibana-plugin-core-public.md | 2 + ...-core-public.resolvedeprecationresponse.md | 16 + ...ugin-core-server.coresetup.deprecations.md | 13 + .../kibana-plugin-core-server.coresetup.md | 1 + ...r.deprecationsdetails.correctiveactions.md | 20 ++ ...er.deprecationsdetails.documentationurl.md | 11 + ...n-core-server.deprecationsdetails.level.md | 13 + ...-plugin-core-server.deprecationsdetails.md | 21 ++ ...core-server.deprecationsdetails.message.md | 11 + ...in-core-server.deprecationsservicesetup.md | 95 ++++++ ...ationsservicesetup.registerdeprecations.md | 11 + ...-server.getdeprecationscontext.esclient.md | 11 + ...ugin-core-server.getdeprecationscontext.md | 19 ++ ...tdeprecationscontext.savedobjectsclient.md | 11 + .../core/server/kibana-plugin-core-server.md | 4 + ...isterdeprecationsconfig.getdeprecations.md | 11 + ...-core-server.registerdeprecationsconfig.md | 18 ++ .../kbn-config/src/config_service.mock.ts | 3 + .../src/config_service.test.mocks.ts | 8 +- .../kbn-config/src/config_service.test.ts | 90 +++++- packages/kbn-config/src/config_service.ts | 21 +- .../deprecation/apply_deprecations.test.ts | 31 +- .../src/deprecation/apply_deprecations.ts | 13 +- .../deprecation/deprecation_factory.test.ts | 171 ++++++++--- .../src/deprecation/deprecation_factory.ts | 87 ++++-- packages/kbn-config/src/deprecation/index.ts | 5 +- packages/kbn-config/src/deprecation/types.ts | 45 ++- packages/kbn-config/src/index.ts | 10 +- src/core/public/core_system.ts | 7 +- .../deprecations/deprecations_client.test.ts | 187 ++++++++++++ .../deprecations/deprecations_client.ts | 78 +++++ .../deprecations/deprecations_service.mock.ts | 36 +++ .../deprecations/deprecations_service.ts | 60 ++++ src/core/public/deprecations/index.ts | 11 + src/core/public/index.ts | 5 + src/core/public/mocks.ts | 4 + src/core/public/plugins/plugin_context.ts | 1 + .../public/plugins/plugins_service.test.ts | 2 + src/core/public/public.api.md | 23 +- .../deprecation/core_deprecations.test.ts | 12 +- .../config/deprecation/core_deprecations.ts | 209 ++++++++----- src/core/server/config/index.ts | 2 +- .../deprecations/deprecations_factory.test.ts | 248 +++++++++++++++ .../deprecations/deprecations_factory.ts | 108 +++++++ .../deprecations_registry.test.ts | 78 +++++ .../deprecations/deprecations_registry.ts | 31 ++ .../deprecations/deprecations_service.mock.ts | 49 +++ .../deprecations/deprecations_service.ts | 168 ++++++++++ src/core/server/deprecations/index.ts | 21 ++ src/core/server/deprecations/routes/get.ts | 35 +++ src/core/server/deprecations/routes/index.ts | 22 ++ src/core/server/deprecations/types.ts | 67 ++++ .../elasticsearch_config.test.ts | 2 +- .../elasticsearch/elasticsearch_config.ts | 32 +- src/core/server/index.ts | 12 +- src/core/server/internal_types.ts | 2 + src/core/server/kibana_config.test.ts | 2 +- src/core/server/kibana_config.ts | 9 +- src/core/server/legacy/legacy_service.test.ts | 2 + src/core/server/legacy/legacy_service.ts | 5 + src/core/server/mocks.ts | 4 + src/core/server/plugins/plugin_context.ts | 1 + src/core/server/server.api.md | 56 +++- src/core/server/server.ts | 11 + src/core/server/types.ts | 1 + src/plugins/kibana_legacy/server/index.ts | 28 +- src/plugins/timelion/server/deprecations.ts | 70 +++++ src/plugins/timelion/server/plugin.ts | 27 +- src/plugins/vis_type_timelion/server/index.ts | 2 +- .../vis_type_timeseries/server/index.ts | 10 +- .../deprecations_service/data.json | 14 + .../deprecations_service/mappings.json | 289 ++++++++++++++++++ test/plugin_functional/config.ts | 3 + .../core_plugin_deprecations/kibana.json | 8 + .../core_plugin_deprecations/package.json | 14 + .../public/application.tsx | 19 ++ .../core_plugin_deprecations/public/index.ts | 19 ++ .../public/plugin.tsx | 40 +++ .../core_plugin_deprecations/server/config.ts | 41 +++ .../core_plugin_deprecations/server/index.ts | 12 + .../core_plugin_deprecations/server/plugin.ts | 57 ++++ .../core_plugin_deprecations/server/routes.ts | 45 +++ .../core_plugin_deprecations/tsconfig.json | 18 ++ .../test_suites/core/deprecations.ts | 247 +++++++++++++++ .../test_suites/core/index.ts | 1 + .../monitoring/server/deprecations.test.js | 68 ++--- .../plugins/monitoring/server/deprecations.ts | 36 +-- .../reporting/server/config/index.test.ts | 2 +- .../plugins/reporting/server/config/index.ts | 8 +- .../server/config_deprecations.test.ts | 2 +- .../security/server/config_deprecations.ts | 38 +-- x-pack/plugins/spaces/server/config.test.ts | 2 +- x-pack/plugins/spaces/server/config.ts | 8 +- .../plugins/task_manager/server/index.test.ts | 2 +- x-pack/plugins/task_manager/server/index.ts | 15 +- 102 files changed, 3262 insertions(+), 347 deletions(-) create mode 100644 docs/development/core/public/kibana-plugin-core-public.corestart.deprecations.md create mode 100644 docs/development/core/public/kibana-plugin-core-public.deprecationsservicestart.getalldeprecations.md create mode 100644 docs/development/core/public/kibana-plugin-core-public.deprecationsservicestart.getdeprecations.md create mode 100644 docs/development/core/public/kibana-plugin-core-public.deprecationsservicestart.isdeprecationresolvable.md create mode 100644 docs/development/core/public/kibana-plugin-core-public.deprecationsservicestart.md create mode 100644 docs/development/core/public/kibana-plugin-core-public.deprecationsservicestart.resolvedeprecation.md create mode 100644 docs/development/core/public/kibana-plugin-core-public.resolvedeprecationresponse.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.coresetup.deprecations.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.deprecationsdetails.correctiveactions.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.deprecationsdetails.documentationurl.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.deprecationsdetails.level.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.deprecationsdetails.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.deprecationsdetails.message.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.deprecationsservicesetup.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.deprecationsservicesetup.registerdeprecations.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.getdeprecationscontext.esclient.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.getdeprecationscontext.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.getdeprecationscontext.savedobjectsclient.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.registerdeprecationsconfig.getdeprecations.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.registerdeprecationsconfig.md create mode 100644 src/core/public/deprecations/deprecations_client.test.ts create mode 100644 src/core/public/deprecations/deprecations_client.ts create mode 100644 src/core/public/deprecations/deprecations_service.mock.ts create mode 100644 src/core/public/deprecations/deprecations_service.ts create mode 100644 src/core/public/deprecations/index.ts create mode 100644 src/core/server/deprecations/deprecations_factory.test.ts create mode 100644 src/core/server/deprecations/deprecations_factory.ts create mode 100644 src/core/server/deprecations/deprecations_registry.test.ts create mode 100644 src/core/server/deprecations/deprecations_registry.ts create mode 100644 src/core/server/deprecations/deprecations_service.mock.ts create mode 100644 src/core/server/deprecations/deprecations_service.ts create mode 100644 src/core/server/deprecations/index.ts create mode 100644 src/core/server/deprecations/routes/get.ts create mode 100644 src/core/server/deprecations/routes/index.ts create mode 100644 src/core/server/deprecations/types.ts create mode 100644 src/plugins/timelion/server/deprecations.ts create mode 100644 test/functional/fixtures/es_archiver/deprecations_service/data.json create mode 100644 test/functional/fixtures/es_archiver/deprecations_service/mappings.json create mode 100644 test/plugin_functional/plugins/core_plugin_deprecations/kibana.json create mode 100644 test/plugin_functional/plugins/core_plugin_deprecations/package.json create mode 100644 test/plugin_functional/plugins/core_plugin_deprecations/public/application.tsx create mode 100644 test/plugin_functional/plugins/core_plugin_deprecations/public/index.ts create mode 100644 test/plugin_functional/plugins/core_plugin_deprecations/public/plugin.tsx create mode 100644 test/plugin_functional/plugins/core_plugin_deprecations/server/config.ts create mode 100644 test/plugin_functional/plugins/core_plugin_deprecations/server/index.ts create mode 100644 test/plugin_functional/plugins/core_plugin_deprecations/server/plugin.ts create mode 100644 test/plugin_functional/plugins/core_plugin_deprecations/server/routes.ts create mode 100644 test/plugin_functional/plugins/core_plugin_deprecations/tsconfig.json create mode 100644 test/plugin_functional/test_suites/core/deprecations.ts diff --git a/docs/development/core/public/kibana-plugin-core-public.corestart.deprecations.md b/docs/development/core/public/kibana-plugin-core-public.corestart.deprecations.md new file mode 100644 index 00000000000000..624c4868d54a7c --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.corestart.deprecations.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [CoreStart](./kibana-plugin-core-public.corestart.md) > [deprecations](./kibana-plugin-core-public.corestart.deprecations.md) + +## CoreStart.deprecations property + +[DeprecationsServiceStart](./kibana-plugin-core-public.deprecationsservicestart.md) + +Signature: + +```typescript +deprecations: DeprecationsServiceStart; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.corestart.md b/docs/development/core/public/kibana-plugin-core-public.corestart.md index a7b45b318d2c9e..6ad9adca53ef5c 100644 --- a/docs/development/core/public/kibana-plugin-core-public.corestart.md +++ b/docs/development/core/public/kibana-plugin-core-public.corestart.md @@ -18,6 +18,7 @@ export interface CoreStart | --- | --- | --- | | [application](./kibana-plugin-core-public.corestart.application.md) | ApplicationStart | [ApplicationStart](./kibana-plugin-core-public.applicationstart.md) | | [chrome](./kibana-plugin-core-public.corestart.chrome.md) | ChromeStart | [ChromeStart](./kibana-plugin-core-public.chromestart.md) | +| [deprecations](./kibana-plugin-core-public.corestart.deprecations.md) | DeprecationsServiceStart | [DeprecationsServiceStart](./kibana-plugin-core-public.deprecationsservicestart.md) | | [docLinks](./kibana-plugin-core-public.corestart.doclinks.md) | DocLinksStart | [DocLinksStart](./kibana-plugin-core-public.doclinksstart.md) | | [fatalErrors](./kibana-plugin-core-public.corestart.fatalerrors.md) | FatalErrorsStart | [FatalErrorsStart](./kibana-plugin-core-public.fatalerrorsstart.md) | | [http](./kibana-plugin-core-public.corestart.http.md) | HttpStart | [HttpStart](./kibana-plugin-core-public.httpstart.md) | diff --git a/docs/development/core/public/kibana-plugin-core-public.deprecationsservicestart.getalldeprecations.md b/docs/development/core/public/kibana-plugin-core-public.deprecationsservicestart.getalldeprecations.md new file mode 100644 index 00000000000000..8175da8a1893a1 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.deprecationsservicestart.getalldeprecations.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [DeprecationsServiceStart](./kibana-plugin-core-public.deprecationsservicestart.md) > [getAllDeprecations](./kibana-plugin-core-public.deprecationsservicestart.getalldeprecations.md) + +## DeprecationsServiceStart.getAllDeprecations property + +Grabs deprecations details for all domains. + +Signature: + +```typescript +getAllDeprecations: () => Promise; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.deprecationsservicestart.getdeprecations.md b/docs/development/core/public/kibana-plugin-core-public.deprecationsservicestart.getdeprecations.md new file mode 100644 index 00000000000000..6e3472b7c3fe3d --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.deprecationsservicestart.getdeprecations.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [DeprecationsServiceStart](./kibana-plugin-core-public.deprecationsservicestart.md) > [getDeprecations](./kibana-plugin-core-public.deprecationsservicestart.getdeprecations.md) + +## DeprecationsServiceStart.getDeprecations property + +Grabs deprecations for a specific domain. + +Signature: + +```typescript +getDeprecations: (domainId: string) => Promise; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.deprecationsservicestart.isdeprecationresolvable.md b/docs/development/core/public/kibana-plugin-core-public.deprecationsservicestart.isdeprecationresolvable.md new file mode 100644 index 00000000000000..842761f6b7ceaf --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.deprecationsservicestart.isdeprecationresolvable.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [DeprecationsServiceStart](./kibana-plugin-core-public.deprecationsservicestart.md) > [isDeprecationResolvable](./kibana-plugin-core-public.deprecationsservicestart.isdeprecationresolvable.md) + +## DeprecationsServiceStart.isDeprecationResolvable property + +Returns a boolean if the provided deprecation can be automatically resolvable. + +Signature: + +```typescript +isDeprecationResolvable: (details: DomainDeprecationDetails) => boolean; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.deprecationsservicestart.md b/docs/development/core/public/kibana-plugin-core-public.deprecationsservicestart.md new file mode 100644 index 00000000000000..0d2c963ec55477 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.deprecationsservicestart.md @@ -0,0 +1,23 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [DeprecationsServiceStart](./kibana-plugin-core-public.deprecationsservicestart.md) + +## DeprecationsServiceStart interface + +DeprecationsService provides methods to fetch domain deprecation details from the Kibana server. + +Signature: + +```typescript +export interface DeprecationsServiceStart +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [getAllDeprecations](./kibana-plugin-core-public.deprecationsservicestart.getalldeprecations.md) | () => Promise<DomainDeprecationDetails[]> | Grabs deprecations details for all domains. | +| [getDeprecations](./kibana-plugin-core-public.deprecationsservicestart.getdeprecations.md) | (domainId: string) => Promise<DomainDeprecationDetails[]> | Grabs deprecations for a specific domain. | +| [isDeprecationResolvable](./kibana-plugin-core-public.deprecationsservicestart.isdeprecationresolvable.md) | (details: DomainDeprecationDetails) => boolean | Returns a boolean if the provided deprecation can be automatically resolvable. | +| [resolveDeprecation](./kibana-plugin-core-public.deprecationsservicestart.resolvedeprecation.md) | (details: DomainDeprecationDetails) => Promise<ResolveDeprecationResponse> | Calls the correctiveActions.api to automatically resolve the depprecation. | + diff --git a/docs/development/core/public/kibana-plugin-core-public.deprecationsservicestart.resolvedeprecation.md b/docs/development/core/public/kibana-plugin-core-public.deprecationsservicestart.resolvedeprecation.md new file mode 100644 index 00000000000000..fae623fed3cc24 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.deprecationsservicestart.resolvedeprecation.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [DeprecationsServiceStart](./kibana-plugin-core-public.deprecationsservicestart.md) > [resolveDeprecation](./kibana-plugin-core-public.deprecationsservicestart.resolvedeprecation.md) + +## DeprecationsServiceStart.resolveDeprecation property + +Calls the correctiveActions.api to automatically resolve the depprecation. + +Signature: + +```typescript +resolveDeprecation: (details: DomainDeprecationDetails) => Promise; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.md b/docs/development/core/public/kibana-plugin-core-public.md index e9d08dcd3bf4cb..32f17d5488f66c 100644 --- a/docs/development/core/public/kibana-plugin-core-public.md +++ b/docs/development/core/public/kibana-plugin-core-public.md @@ -59,6 +59,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [ChromeUserBanner](./kibana-plugin-core-public.chromeuserbanner.md) | | | [CoreSetup](./kibana-plugin-core-public.coresetup.md) | Core services exposed to the Plugin setup lifecycle | | [CoreStart](./kibana-plugin-core-public.corestart.md) | Core services exposed to the Plugin start lifecycle | +| [DeprecationsServiceStart](./kibana-plugin-core-public.deprecationsservicestart.md) | DeprecationsService provides methods to fetch domain deprecation details from the Kibana server. | | [DocLinksStart](./kibana-plugin-core-public.doclinksstart.md) | | | [ErrorToastOptions](./kibana-plugin-core-public.errortoastoptions.md) | Options available for [IToasts](./kibana-plugin-core-public.itoasts.md) error APIs. | | [FatalErrorInfo](./kibana-plugin-core-public.fatalerrorinfo.md) | Represents the message and stack of a fatal Error | @@ -164,6 +165,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [PublicAppMetaInfo](./kibana-plugin-core-public.publicappmetainfo.md) | Public information about a registered app's [keywords](./kibana-plugin-core-public.appmeta.md) | | [PublicAppSearchDeepLinkInfo](./kibana-plugin-core-public.publicappsearchdeeplinkinfo.md) | Public information about a registered app's [searchDeepLinks](./kibana-plugin-core-public.appsearchdeeplink.md) | | [PublicUiSettingsParams](./kibana-plugin-core-public.publicuisettingsparams.md) | A sub-set of [UiSettingsParams](./kibana-plugin-core-public.uisettingsparams.md) exposed to the client-side. | +| [ResolveDeprecationResponse](./kibana-plugin-core-public.resolvedeprecationresponse.md) | | | [SavedObjectAttribute](./kibana-plugin-core-public.savedobjectattribute.md) | Type definition for a Saved Object attribute value | | [SavedObjectAttributeSingle](./kibana-plugin-core-public.savedobjectattributesingle.md) | Don't use this type, it's simply a helper type for [SavedObjectAttribute](./kibana-plugin-core-public.savedobjectattribute.md) | | [SavedObjectsClientContract](./kibana-plugin-core-public.savedobjectsclientcontract.md) | SavedObjectsClientContract as implemented by the [SavedObjectsClient](./kibana-plugin-core-public.savedobjectsclient.md) | diff --git a/docs/development/core/public/kibana-plugin-core-public.resolvedeprecationresponse.md b/docs/development/core/public/kibana-plugin-core-public.resolvedeprecationresponse.md new file mode 100644 index 00000000000000..928bf8c07004e0 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.resolvedeprecationresponse.md @@ -0,0 +1,16 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [ResolveDeprecationResponse](./kibana-plugin-core-public.resolvedeprecationresponse.md) + +## ResolveDeprecationResponse type + +Signature: + +```typescript +export declare type ResolveDeprecationResponse = { + status: 'ok'; +} | { + status: 'fail'; + reason: string; +}; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.coresetup.deprecations.md b/docs/development/core/server/kibana-plugin-core-server.coresetup.deprecations.md new file mode 100644 index 00000000000000..436cc29b6e3438 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.coresetup.deprecations.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [CoreSetup](./kibana-plugin-core-server.coresetup.md) > [deprecations](./kibana-plugin-core-server.coresetup.deprecations.md) + +## CoreSetup.deprecations property + +[DeprecationsServiceSetup](./kibana-plugin-core-server.deprecationsservicesetup.md) + +Signature: + +```typescript +deprecations: DeprecationsServiceSetup; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.coresetup.md b/docs/development/core/server/kibana-plugin-core-server.coresetup.md index 1171dbad570ce5..b37ac80db87d64 100644 --- a/docs/development/core/server/kibana-plugin-core-server.coresetup.md +++ b/docs/development/core/server/kibana-plugin-core-server.coresetup.md @@ -18,6 +18,7 @@ export interface CoreSetupCapabilitiesSetup | [CapabilitiesSetup](./kibana-plugin-core-server.capabilitiessetup.md) | | [context](./kibana-plugin-core-server.coresetup.context.md) | ContextSetup | [ContextSetup](./kibana-plugin-core-server.contextsetup.md) | +| [deprecations](./kibana-plugin-core-server.coresetup.deprecations.md) | DeprecationsServiceSetup | [DeprecationsServiceSetup](./kibana-plugin-core-server.deprecationsservicesetup.md) | | [elasticsearch](./kibana-plugin-core-server.coresetup.elasticsearch.md) | ElasticsearchServiceSetup | [ElasticsearchServiceSetup](./kibana-plugin-core-server.elasticsearchservicesetup.md) | | [getStartServices](./kibana-plugin-core-server.coresetup.getstartservices.md) | StartServicesAccessor<TPluginsStart, TStart> | [StartServicesAccessor](./kibana-plugin-core-server.startservicesaccessor.md) | | [http](./kibana-plugin-core-server.coresetup.http.md) | HttpServiceSetup & {
resources: HttpResources;
} | [HttpServiceSetup](./kibana-plugin-core-server.httpservicesetup.md) | diff --git a/docs/development/core/server/kibana-plugin-core-server.deprecationsdetails.correctiveactions.md b/docs/development/core/server/kibana-plugin-core-server.deprecationsdetails.correctiveactions.md new file mode 100644 index 00000000000000..e362bc4e0329c4 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.deprecationsdetails.correctiveactions.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [DeprecationsDetails](./kibana-plugin-core-server.deprecationsdetails.md) > [correctiveActions](./kibana-plugin-core-server.deprecationsdetails.correctiveactions.md) + +## DeprecationsDetails.correctiveActions property + +Signature: + +```typescript +correctiveActions: { + api?: { + path: string; + method: 'POST' | 'PUT'; + body?: { + [key: string]: any; + }; + }; + manualSteps?: string[]; + }; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.deprecationsdetails.documentationurl.md b/docs/development/core/server/kibana-plugin-core-server.deprecationsdetails.documentationurl.md new file mode 100644 index 00000000000000..467d6d76cf842d --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.deprecationsdetails.documentationurl.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [DeprecationsDetails](./kibana-plugin-core-server.deprecationsdetails.md) > [documentationUrl](./kibana-plugin-core-server.deprecationsdetails.documentationurl.md) + +## DeprecationsDetails.documentationUrl property + +Signature: + +```typescript +documentationUrl?: string; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.deprecationsdetails.level.md b/docs/development/core/server/kibana-plugin-core-server.deprecationsdetails.level.md new file mode 100644 index 00000000000000..64ad22e2c87fbe --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.deprecationsdetails.level.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [DeprecationsDetails](./kibana-plugin-core-server.deprecationsdetails.md) > [level](./kibana-plugin-core-server.deprecationsdetails.level.md) + +## DeprecationsDetails.level property + +levels: - warning: will not break deployment upon upgrade - critical: needs to be addressed before upgrade. - fetch\_error: Deprecations service failed to grab the deprecation details for the domain. + +Signature: + +```typescript +level: 'warning' | 'critical' | 'fetch_error'; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.deprecationsdetails.md b/docs/development/core/server/kibana-plugin-core-server.deprecationsdetails.md new file mode 100644 index 00000000000000..bb77e4247711f0 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.deprecationsdetails.md @@ -0,0 +1,21 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [DeprecationsDetails](./kibana-plugin-core-server.deprecationsdetails.md) + +## DeprecationsDetails interface + +Signature: + +```typescript +export interface DeprecationsDetails +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [correctiveActions](./kibana-plugin-core-server.deprecationsdetails.correctiveactions.md) | {
api?: {
path: string;
method: 'POST' | 'PUT';
body?: {
[key: string]: any;
};
};
manualSteps?: string[];
} | | +| [documentationUrl](./kibana-plugin-core-server.deprecationsdetails.documentationurl.md) | string | | +| [level](./kibana-plugin-core-server.deprecationsdetails.level.md) | 'warning' | 'critical' | 'fetch_error' | levels: - warning: will not break deployment upon upgrade - critical: needs to be addressed before upgrade. - fetch\_error: Deprecations service failed to grab the deprecation details for the domain. | +| [message](./kibana-plugin-core-server.deprecationsdetails.message.md) | string | | + diff --git a/docs/development/core/server/kibana-plugin-core-server.deprecationsdetails.message.md b/docs/development/core/server/kibana-plugin-core-server.deprecationsdetails.message.md new file mode 100644 index 00000000000000..d79a4c9bd7995d --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.deprecationsdetails.message.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [DeprecationsDetails](./kibana-plugin-core-server.deprecationsdetails.md) > [message](./kibana-plugin-core-server.deprecationsdetails.message.md) + +## DeprecationsDetails.message property + +Signature: + +```typescript +message: string; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.deprecationsservicesetup.md b/docs/development/core/server/kibana-plugin-core-server.deprecationsservicesetup.md new file mode 100644 index 00000000000000..7d9d3dcdda4dae --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.deprecationsservicesetup.md @@ -0,0 +1,95 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [DeprecationsServiceSetup](./kibana-plugin-core-server.deprecationsservicesetup.md) + +## DeprecationsServiceSetup interface + +The deprecations service provides a way for the Kibana platform to communicate deprecated features and configs with its users. These deprecations are only communicated if the deployment is using these features. Allowing for a user tailored experience for upgrading the stack version. + +The Deprecation service is consumed by the upgrade assistant to assist with the upgrade experience. + +If a deprecated feature can be resolved without manual user intervention. Using correctiveActions.api allows the Upgrade Assistant to use this api to correct the deprecation upon a user trigger. + +Signature: + +```typescript +export interface DeprecationsServiceSetup +``` + +## Example + + +```ts +import { DeprecationsDetails, GetDeprecationsContext, CoreSetup } from 'src/core/server'; + +async function getDeprecations({ esClient, savedObjectsClient }: GetDeprecationsContext): Promise { + const deprecations: DeprecationsDetails[] = []; + const count = await getTimelionSheetsCount(savedObjectsClient); + + if (count > 0) { + // Example of a manual correctiveAction + deprecations.push({ + message: `You have ${count} Timelion worksheets. The Timelion app will be removed in 8.0. To continue using your Timelion worksheets, migrate them to a dashboard.`, + documentationUrl: + 'https://www.elastic.co/guide/en/kibana/current/create-panels-with-timelion.html', + level: 'warning', + correctiveActions: { + manualSteps: [ + 'Navigate to the Kibana Dashboard and click "Create dashboard".', + 'Select Timelion from the "New Visualization" window.', + 'Open a new tab, open the Timelion app, select the chart you want to copy, then copy the chart expression.', + 'Go to Timelion, paste the chart expression in the Timelion expression field, then click Update.', + 'In the toolbar, click Save.', + 'On the Save visualization window, enter the visualization Title, then click Save and return.', + ], + }, + }); + } + + // Example of an api correctiveAction + deprecations.push({ + "message": "User 'test_dashboard_user' is using a deprecated role: 'kibana_user'", + "documentationUrl": "https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-put-user.html", + "level": "critical", + "correctiveActions": { + "api": { + "path": "/internal/security/users/test_dashboard_user", + "method": "POST", + "body": { + "username": "test_dashboard_user", + "roles": [ + "machine_learning_user", + "enrich_user", + "kibana_admin" + ], + "full_name": "Alison Goryachev", + "email": "alisongoryachev@gmail.com", + "metadata": {}, + "enabled": true + } + }, + "manualSteps": [ + "Using Kibana user management, change all users using the kibana_user role to the kibana_admin role.", + "Using Kibana role-mapping management, change all role-mappings which assing the kibana_user role to the kibana_admin role." + ] + }, + }); + + return deprecations; +} + + +export class Plugin() { + setup: (core: CoreSetup) => { + core.deprecations.registerDeprecations({ getDeprecations }); + } +} + +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [registerDeprecations](./kibana-plugin-core-server.deprecationsservicesetup.registerdeprecations.md) | (deprecationContext: RegisterDeprecationsConfig) => void | | + diff --git a/docs/development/core/server/kibana-plugin-core-server.deprecationsservicesetup.registerdeprecations.md b/docs/development/core/server/kibana-plugin-core-server.deprecationsservicesetup.registerdeprecations.md new file mode 100644 index 00000000000000..07c2a3ad0ce556 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.deprecationsservicesetup.registerdeprecations.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [DeprecationsServiceSetup](./kibana-plugin-core-server.deprecationsservicesetup.md) > [registerDeprecations](./kibana-plugin-core-server.deprecationsservicesetup.registerdeprecations.md) + +## DeprecationsServiceSetup.registerDeprecations property + +Signature: + +```typescript +registerDeprecations: (deprecationContext: RegisterDeprecationsConfig) => void; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.getdeprecationscontext.esclient.md b/docs/development/core/server/kibana-plugin-core-server.getdeprecationscontext.esclient.md new file mode 100644 index 00000000000000..70c1864bf905f0 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.getdeprecationscontext.esclient.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [GetDeprecationsContext](./kibana-plugin-core-server.getdeprecationscontext.md) > [esClient](./kibana-plugin-core-server.getdeprecationscontext.esclient.md) + +## GetDeprecationsContext.esClient property + +Signature: + +```typescript +esClient: IScopedClusterClient; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.getdeprecationscontext.md b/docs/development/core/server/kibana-plugin-core-server.getdeprecationscontext.md new file mode 100644 index 00000000000000..1018444f0849ad --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.getdeprecationscontext.md @@ -0,0 +1,19 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [GetDeprecationsContext](./kibana-plugin-core-server.getdeprecationscontext.md) + +## GetDeprecationsContext interface + +Signature: + +```typescript +export interface GetDeprecationsContext +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [esClient](./kibana-plugin-core-server.getdeprecationscontext.esclient.md) | IScopedClusterClient | | +| [savedObjectsClient](./kibana-plugin-core-server.getdeprecationscontext.savedobjectsclient.md) | SavedObjectsClientContract | | + diff --git a/docs/development/core/server/kibana-plugin-core-server.getdeprecationscontext.savedobjectsclient.md b/docs/development/core/server/kibana-plugin-core-server.getdeprecationscontext.savedobjectsclient.md new file mode 100644 index 00000000000000..66da52d3b58248 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.getdeprecationscontext.savedobjectsclient.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [GetDeprecationsContext](./kibana-plugin-core-server.getdeprecationscontext.md) > [savedObjectsClient](./kibana-plugin-core-server.getdeprecationscontext.savedobjectsclient.md) + +## GetDeprecationsContext.savedObjectsClient property + +Signature: + +```typescript +savedObjectsClient: SavedObjectsClientContract; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.md b/docs/development/core/server/kibana-plugin-core-server.md index 4bf00d2da6e23a..faac8108de8254 100644 --- a/docs/development/core/server/kibana-plugin-core-server.md +++ b/docs/development/core/server/kibana-plugin-core-server.md @@ -69,13 +69,16 @@ The plugin integrates with the core system via lifecycle events: `setup` | [DeprecationAPIClientParams](./kibana-plugin-core-server.deprecationapiclientparams.md) | | | [DeprecationAPIResponse](./kibana-plugin-core-server.deprecationapiresponse.md) | | | [DeprecationInfo](./kibana-plugin-core-server.deprecationinfo.md) | | +| [DeprecationsDetails](./kibana-plugin-core-server.deprecationsdetails.md) | | | [DeprecationSettings](./kibana-plugin-core-server.deprecationsettings.md) | UiSettings deprecation field options. | +| [DeprecationsServiceSetup](./kibana-plugin-core-server.deprecationsservicesetup.md) | The deprecations service provides a way for the Kibana platform to communicate deprecated features and configs with its users. These deprecations are only communicated if the deployment is using these features. Allowing for a user tailored experience for upgrading the stack version.The Deprecation service is consumed by the upgrade assistant to assist with the upgrade experience.If a deprecated feature can be resolved without manual user intervention. Using correctiveActions.api allows the Upgrade Assistant to use this api to correct the deprecation upon a user trigger. | | [DiscoveredPlugin](./kibana-plugin-core-server.discoveredplugin.md) | Small container object used to expose information about discovered plugins that may or may not have been started. | | [ElasticsearchServiceSetup](./kibana-plugin-core-server.elasticsearchservicesetup.md) | | | [ElasticsearchServiceStart](./kibana-plugin-core-server.elasticsearchservicestart.md) | | | [ElasticsearchStatusMeta](./kibana-plugin-core-server.elasticsearchstatusmeta.md) | | | [ErrorHttpResponseOptions](./kibana-plugin-core-server.errorhttpresponseoptions.md) | HTTP response parameters | | [FakeRequest](./kibana-plugin-core-server.fakerequest.md) | Fake request object created manually by Kibana plugins. | +| [GetDeprecationsContext](./kibana-plugin-core-server.getdeprecationscontext.md) | | | [GetResponse](./kibana-plugin-core-server.getresponse.md) | | | [HttpAuth](./kibana-plugin-core-server.httpauth.md) | | | [HttpResources](./kibana-plugin-core-server.httpresources.md) | HttpResources service is responsible for serving static & dynamic assets for Kibana application via HTTP. Provides API allowing plug-ins to respond with: - a pre-configured HTML page bootstrapping Kibana client app - custom HTML page - custom JS script file. | @@ -128,6 +131,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [PluginConfigDescriptor](./kibana-plugin-core-server.pluginconfigdescriptor.md) | Describes a plugin configuration properties. | | [PluginInitializerContext](./kibana-plugin-core-server.plugininitializercontext.md) | Context that's available to plugins during initialization stage. | | [PluginManifest](./kibana-plugin-core-server.pluginmanifest.md) | Describes the set of required and optional properties plugin can define in its mandatory JSON manifest file. | +| [RegisterDeprecationsConfig](./kibana-plugin-core-server.registerdeprecationsconfig.md) | | | [RequestHandlerContext](./kibana-plugin-core-server.requesthandlercontext.md) | Plugin specific context passed to a route handler.Provides the following clients and services: - [savedObjects.client](./kibana-plugin-core-server.savedobjectsclient.md) - Saved Objects client which uses the credentials of the incoming request - [savedObjects.typeRegistry](./kibana-plugin-core-server.isavedobjecttyperegistry.md) - Type registry containing all the registered types. - [elasticsearch.client](./kibana-plugin-core-server.iscopedclusterclient.md) - Elasticsearch data client which uses the credentials of the incoming request - [elasticsearch.legacy.client](./kibana-plugin-core-server.legacyscopedclusterclient.md) - The legacy Elasticsearch data client which uses the credentials of the incoming request - [uiSettings.client](./kibana-plugin-core-server.iuisettingsclient.md) - uiSettings client which uses the credentials of the incoming request | | [ResolveCapabilitiesOptions](./kibana-plugin-core-server.resolvecapabilitiesoptions.md) | Defines a set of additional options for the resolveCapabilities method of [CapabilitiesStart](./kibana-plugin-core-server.capabilitiesstart.md). | | [RouteConfig](./kibana-plugin-core-server.routeconfig.md) | Route specific configuration. | diff --git a/docs/development/core/server/kibana-plugin-core-server.registerdeprecationsconfig.getdeprecations.md b/docs/development/core/server/kibana-plugin-core-server.registerdeprecationsconfig.getdeprecations.md new file mode 100644 index 00000000000000..cf008725ff15b9 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.registerdeprecationsconfig.getdeprecations.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [RegisterDeprecationsConfig](./kibana-plugin-core-server.registerdeprecationsconfig.md) > [getDeprecations](./kibana-plugin-core-server.registerdeprecationsconfig.getdeprecations.md) + +## RegisterDeprecationsConfig.getDeprecations property + +Signature: + +```typescript +getDeprecations: (context: GetDeprecationsContext) => MaybePromise; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.registerdeprecationsconfig.md b/docs/development/core/server/kibana-plugin-core-server.registerdeprecationsconfig.md new file mode 100644 index 00000000000000..59e6d406f84bf3 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.registerdeprecationsconfig.md @@ -0,0 +1,18 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [RegisterDeprecationsConfig](./kibana-plugin-core-server.registerdeprecationsconfig.md) + +## RegisterDeprecationsConfig interface + +Signature: + +```typescript +export interface RegisterDeprecationsConfig +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [getDeprecations](./kibana-plugin-core-server.registerdeprecationsconfig.getdeprecations.md) | (context: GetDeprecationsContext) => MaybePromise<DeprecationsDetails[]> | | + diff --git a/packages/kbn-config/src/config_service.mock.ts b/packages/kbn-config/src/config_service.mock.ts index 638627caf1e50d..83fbf20b5c0b3d 100644 --- a/packages/kbn-config/src/config_service.mock.ts +++ b/packages/kbn-config/src/config_service.mock.ts @@ -25,13 +25,16 @@ const createConfigServiceMock = ({ setSchema: jest.fn(), addDeprecationProvider: jest.fn(), validate: jest.fn(), + getHandledDeprecatedConfigs: jest.fn(), }; + mocked.atPath.mockReturnValue(new BehaviorSubject(atPath)); mocked.atPathSync.mockReturnValue(atPath); mocked.getConfig$.mockReturnValue(new BehaviorSubject(new ObjectToConfigAdapter(getConfig$))); mocked.getUsedPaths.mockResolvedValue([]); mocked.getUnusedPaths.mockResolvedValue([]); mocked.isEnabledAtPath.mockResolvedValue(true); + mocked.getHandledDeprecatedConfigs.mockReturnValue([]); return mocked; }; diff --git a/packages/kbn-config/src/config_service.test.mocks.ts b/packages/kbn-config/src/config_service.test.mocks.ts index 99539726c3e437..d8da2852b92515 100644 --- a/packages/kbn-config/src/config_service.test.mocks.ts +++ b/packages/kbn-config/src/config_service.test.mocks.ts @@ -7,9 +7,15 @@ */ export const mockPackage = new Proxy({ raw: {} as any }, { get: (obj, prop) => obj.raw[prop] }); +import type { applyDeprecations } from './deprecation/apply_deprecations'; + jest.mock('../../../package.json', () => mockPackage); -export const mockApplyDeprecations = jest.fn((config, deprecations, log) => config); +export const mockApplyDeprecations = jest.fn< + Record, + Parameters +>((config, deprecations, createAddDeprecation) => config); + jest.mock('./deprecation/apply_deprecations', () => ({ applyDeprecations: mockApplyDeprecations, })); diff --git a/packages/kbn-config/src/config_service.test.ts b/packages/kbn-config/src/config_service.test.ts index e38fff866df894..64404341bc64d0 100644 --- a/packages/kbn-config/src/config_service.test.ts +++ b/packages/kbn-config/src/config_service.test.ts @@ -72,10 +72,10 @@ test('throws if config at path does not match schema', async () => { ); await expect(valuesReceived).toMatchInlineSnapshot(` - Array [ - [Error: [config validation of [key]]: expected value of type [string] but got [number]], - ] - `); + Array [ + [Error: [config validation of [key]]: expected value of type [string] but got [number]], + ] + `); }); test('re-validate config when updated', async () => { @@ -97,11 +97,11 @@ test('re-validate config when updated', async () => { rawConfig$.next({ key: 123 }); - await expect(valuesReceived).toMatchInlineSnapshot(` - Array [ - "value", - [Error: [config validation of [key]]: expected value of type [string] but got [number]], - ] + expect(valuesReceived).toMatchInlineSnapshot(` + Array [ + "value", + [Error: [config validation of [key]]: expected value of type [string] but got [number]], + ] `); }); @@ -416,10 +416,10 @@ test('throws during validation is any schema is invalid', async () => { test('logs deprecation warning during validation', async () => { const rawConfig = getRawConfigProvider({}); const configService = new ConfigService(rawConfig, defaultEnv, logger); - - mockApplyDeprecations.mockImplementationOnce((config, deprecations, log) => { - log('some deprecation message'); - log('another deprecation message'); + mockApplyDeprecations.mockImplementationOnce((config, deprecations, createAddDeprecation) => { + const addDeprecation = createAddDeprecation!(''); + addDeprecation({ message: 'some deprecation message' }); + addDeprecation({ message: 'another deprecation message' }); return config; }); @@ -437,6 +437,37 @@ test('logs deprecation warning during validation', async () => { `); }); +test('does not log warnings for silent deprecations during validation', async () => { + const rawConfig = getRawConfigProvider({}); + const configService = new ConfigService(rawConfig, defaultEnv, logger); + + mockApplyDeprecations + .mockImplementationOnce((config, deprecations, createAddDeprecation) => { + const addDeprecation = createAddDeprecation!(''); + addDeprecation({ message: 'some deprecation message', silent: true }); + addDeprecation({ message: 'another deprecation message' }); + return config; + }) + .mockImplementationOnce((config, deprecations, createAddDeprecation) => { + const addDeprecation = createAddDeprecation!(''); + addDeprecation({ message: 'I am silent', silent: true }); + return config; + }); + + loggerMock.clear(logger); + await configService.validate(); + expect(loggerMock.collect(logger).warn).toMatchInlineSnapshot(` + Array [ + Array [ + "another deprecation message", + ], + ] + `); + loggerMock.clear(logger); + await configService.validate(); + expect(loggerMock.collect(logger).warn).toMatchInlineSnapshot(`Array []`); +}); + describe('atPathSync', () => { test('returns the value at path', async () => { const rawConfig = getRawConfigProvider({ key: 'foo' }); @@ -477,3 +508,36 @@ describe('atPathSync', () => { expect(configService.atPathSync('key')).toEqual('new-value'); }); }); + +describe('getHandledDeprecatedConfigs', () => { + it('returns all handled deprecated configs', async () => { + const rawConfig = getRawConfigProvider({ base: { unused: 'unusedConfig' } }); + const configService = new ConfigService(rawConfig, defaultEnv, logger); + + configService.addDeprecationProvider('base', ({ unused }) => [unused('unused')]); + + mockApplyDeprecations.mockImplementationOnce((config, deprecations, createAddDeprecation) => { + deprecations.forEach((deprecation) => { + const addDeprecation = createAddDeprecation!(deprecation.path); + addDeprecation({ message: `some deprecation message`, documentationUrl: 'some-url' }); + }); + return config; + }); + + await configService.validate(); + + expect(configService.getHandledDeprecatedConfigs()).toMatchInlineSnapshot(` + Array [ + Array [ + "base", + Array [ + Object { + "documentationUrl": "some-url", + "message": "some deprecation message", + }, + ], + ], + ] + `); + }); +}); diff --git a/packages/kbn-config/src/config_service.ts b/packages/kbn-config/src/config_service.ts index d71327350d2124..91927b4c7b5c96 100644 --- a/packages/kbn-config/src/config_service.ts +++ b/packages/kbn-config/src/config_service.ts @@ -21,6 +21,7 @@ import { ConfigDeprecationWithContext, ConfigDeprecationProvider, configDeprecationFactory, + DeprecatedConfigDetails, } from './deprecation'; import { LegacyObjectToConfigAdapter } from './legacy'; @@ -43,6 +44,7 @@ export class ConfigService { private readonly handledPaths: Set = new Set(); private readonly schemas = new Map>(); private readonly deprecations = new BehaviorSubject([]); + private readonly handledDeprecatedConfigs = new Map(); constructor( private readonly rawConfigProvider: RawConfigurationProvider, @@ -91,6 +93,13 @@ export class ConfigService { ]); } + /** + * returns all handled deprecated configs + */ + public getHandledDeprecatedConfigs() { + return [...this.handledDeprecatedConfigs.entries()]; + } + /** * Validate the whole configuration and log the deprecation warnings. * @@ -186,8 +195,16 @@ export class ConfigService { const rawConfig = await this.rawConfigProvider.getConfig$().pipe(take(1)).toPromise(); const deprecations = await this.deprecations.pipe(take(1)).toPromise(); const deprecationMessages: string[] = []; - const logger = (msg: string) => deprecationMessages.push(msg); - applyDeprecations(rawConfig, deprecations, logger); + const createAddDeprecation = (domainId: string) => (context: DeprecatedConfigDetails) => { + if (!context.silent) { + deprecationMessages.push(context.message); + } + const handledDeprecatedConfig = this.handledDeprecatedConfigs.get(domainId) || []; + handledDeprecatedConfig.push(context); + this.handledDeprecatedConfigs.set(domainId, handledDeprecatedConfig); + }; + + applyDeprecations(rawConfig, deprecations, createAddDeprecation); deprecationMessages.forEach((msg) => { this.deprecationLog.warn(msg); }); diff --git a/packages/kbn-config/src/deprecation/apply_deprecations.test.ts b/packages/kbn-config/src/deprecation/apply_deprecations.test.ts index 9e058faf68052c..f2c0a439163432 100644 --- a/packages/kbn-config/src/deprecation/apply_deprecations.test.ts +++ b/packages/kbn-config/src/deprecation/apply_deprecations.test.ts @@ -32,8 +32,31 @@ describe('applyDeprecations', () => { expect(handlerC).toHaveBeenCalledTimes(1); }); + it('passes path to addDeprecation factory', () => { + const addDeprecation = jest.fn(); + const createAddDeprecation = jest.fn().mockReturnValue(addDeprecation); + const initialConfig = { foo: 'bar', deprecated: 'deprecated' }; + const alteredConfig = { foo: 'bar' }; + + const handlerA = jest.fn().mockReturnValue(alteredConfig); + const handlerB = jest.fn().mockImplementation((conf) => conf); + + applyDeprecations( + initialConfig, + [wrapHandler(handlerA, 'pathA'), wrapHandler(handlerB, 'pathB')], + createAddDeprecation + ); + + expect(handlerA).toHaveBeenCalledWith(initialConfig, 'pathA', addDeprecation); + expect(handlerB).toHaveBeenCalledWith(alteredConfig, 'pathB', addDeprecation); + expect(createAddDeprecation).toBeCalledTimes(2); + expect(createAddDeprecation).toHaveBeenNthCalledWith(1, 'pathA'); + expect(createAddDeprecation).toHaveBeenNthCalledWith(2, 'pathB'); + }); + it('calls handlers with correct arguments', () => { - const logger = () => undefined; + const addDeprecation = jest.fn(); + const createAddDeprecation = jest.fn().mockReturnValue(addDeprecation); const initialConfig = { foo: 'bar', deprecated: 'deprecated' }; const alteredConfig = { foo: 'bar' }; @@ -43,11 +66,11 @@ describe('applyDeprecations', () => { applyDeprecations( initialConfig, [wrapHandler(handlerA, 'pathA'), wrapHandler(handlerB, 'pathB')], - logger + createAddDeprecation ); - expect(handlerA).toHaveBeenCalledWith(initialConfig, 'pathA', logger); - expect(handlerB).toHaveBeenCalledWith(alteredConfig, 'pathB', logger); + expect(handlerA).toHaveBeenCalledWith(initialConfig, 'pathA', addDeprecation); + expect(handlerB).toHaveBeenCalledWith(alteredConfig, 'pathB', addDeprecation); }); it('returns the migrated config', () => { diff --git a/packages/kbn-config/src/deprecation/apply_deprecations.ts b/packages/kbn-config/src/deprecation/apply_deprecations.ts index 0813440adb57c8..6aced541dc30d4 100644 --- a/packages/kbn-config/src/deprecation/apply_deprecations.ts +++ b/packages/kbn-config/src/deprecation/apply_deprecations.ts @@ -7,23 +7,24 @@ */ import { cloneDeep } from 'lodash'; -import { ConfigDeprecationWithContext, ConfigDeprecationLogger } from './types'; - -const noopLogger = (msg: string) => undefined; +import { ConfigDeprecationWithContext, AddConfigDeprecation } from './types'; +const noopAddDeprecationFactory: () => AddConfigDeprecation = () => () => undefined; /** - * Applies deprecations on given configuration and logs any deprecation warning using provided logger. + * Applies deprecations on given configuration and passes addDeprecation hook. + * This hook is used for logging any deprecation warning using provided logger. + * This hook is used for exposing deprecated configs that must be handled by the user before upgrading to next major. * * @internal */ export const applyDeprecations = ( config: Record, deprecations: ConfigDeprecationWithContext[], - logger: ConfigDeprecationLogger = noopLogger + createAddDeprecation: (pluginId: string) => AddConfigDeprecation = noopAddDeprecationFactory ) => { let processed = cloneDeep(config); deprecations.forEach(({ deprecation, path }) => { - processed = deprecation(processed, path, logger); + processed = deprecation(processed, path, createAddDeprecation(path)); }); return processed; }; diff --git a/packages/kbn-config/src/deprecation/deprecation_factory.test.ts b/packages/kbn-config/src/deprecation/deprecation_factory.test.ts index ba8a0cbf7ca574..11a49ed79d1701 100644 --- a/packages/kbn-config/src/deprecation/deprecation_factory.test.ts +++ b/packages/kbn-config/src/deprecation/deprecation_factory.test.ts @@ -6,17 +6,16 @@ * Side Public License, v 1. */ -import { ConfigDeprecationLogger } from './types'; +import { DeprecatedConfigDetails } from './types'; import { configDeprecationFactory } from './deprecation_factory'; describe('DeprecationFactory', () => { const { rename, unused, renameFromRoot, unusedFromRoot } = configDeprecationFactory; - let deprecationMessages: string[]; - const logger: ConfigDeprecationLogger = (msg) => deprecationMessages.push(msg); + const addDeprecation = jest.fn(); beforeEach(() => { - deprecationMessages = []; + addDeprecation.mockClear(); }); describe('rename', () => { @@ -30,7 +29,7 @@ describe('DeprecationFactory', () => { property: 'value', }, }; - const processed = rename('deprecated', 'renamed')(rawConfig, 'myplugin', logger); + const processed = rename('deprecated', 'renamed')(rawConfig, 'myplugin', addDeprecation); expect(processed).toEqual({ myplugin: { renamed: 'toberenamed', @@ -40,9 +39,18 @@ describe('DeprecationFactory', () => { property: 'value', }, }); - expect(deprecationMessages).toMatchInlineSnapshot(` + expect(addDeprecation.mock.calls).toMatchInlineSnapshot(` Array [ - "\\"myplugin.deprecated\\" is deprecated and has been replaced by \\"myplugin.renamed\\"", + Array [ + Object { + "correctiveActions": Object { + "manualSteps": Array [ + "Replace \\"myplugin.deprecated\\" with \\"myplugin.renamed\\" in the Kibana config file, CLI flag, or environment variable (in Docker only).", + ], + }, + "message": "\\"myplugin.deprecated\\" is deprecated and has been replaced by \\"myplugin.renamed\\"", + }, + ], ] `); }); @@ -56,7 +64,7 @@ describe('DeprecationFactory', () => { property: 'value', }, }; - const processed = rename('deprecated', 'new')(rawConfig, 'myplugin', logger); + const processed = rename('deprecated', 'new')(rawConfig, 'myplugin', addDeprecation); expect(processed).toEqual({ myplugin: { new: 'new', @@ -66,7 +74,7 @@ describe('DeprecationFactory', () => { property: 'value', }, }); - expect(deprecationMessages.length).toEqual(0); + expect(addDeprecation).toHaveBeenCalledTimes(0); }); it('handles nested keys', () => { const rawConfig = { @@ -83,7 +91,7 @@ describe('DeprecationFactory', () => { const processed = rename('oldsection.deprecated', 'newsection.renamed')( rawConfig, 'myplugin', - logger + addDeprecation ); expect(processed).toEqual({ myplugin: { @@ -97,9 +105,18 @@ describe('DeprecationFactory', () => { property: 'value', }, }); - expect(deprecationMessages).toMatchInlineSnapshot(` + expect(addDeprecation.mock.calls).toMatchInlineSnapshot(` Array [ - "\\"myplugin.oldsection.deprecated\\" is deprecated and has been replaced by \\"myplugin.newsection.renamed\\"", + Array [ + Object { + "correctiveActions": Object { + "manualSteps": Array [ + "Replace \\"myplugin.oldsection.deprecated\\" with \\"myplugin.newsection.renamed\\" in the Kibana config file, CLI flag, or environment variable (in Docker only).", + ], + }, + "message": "\\"myplugin.oldsection.deprecated\\" is deprecated and has been replaced by \\"myplugin.newsection.renamed\\"", + }, + ], ] `); }); @@ -110,15 +127,25 @@ describe('DeprecationFactory', () => { renamed: 'renamed', }, }; - const processed = rename('deprecated', 'renamed')(rawConfig, 'myplugin', logger); + const processed = rename('deprecated', 'renamed')(rawConfig, 'myplugin', addDeprecation); expect(processed).toEqual({ myplugin: { renamed: 'renamed', }, }); - expect(deprecationMessages).toMatchInlineSnapshot(` + expect(addDeprecation.mock.calls).toMatchInlineSnapshot(` Array [ - "\\"myplugin.deprecated\\" is deprecated and has been replaced by \\"myplugin.renamed\\". However both key are present, ignoring \\"myplugin.deprecated\\"", + Array [ + Object { + "correctiveActions": Object { + "manualSteps": Array [ + "Make sure \\"myplugin.renamed\\" contains the correct value in the config file, CLI flag, or environment variable (in Docker only).", + "Remove \\"myplugin.deprecated\\" from the config.", + ], + }, + "message": "\\"myplugin.deprecated\\" is deprecated and has been replaced by \\"myplugin.renamed\\". However both key are present, ignoring \\"myplugin.deprecated\\"", + }, + ], ] `); }); @@ -138,7 +165,7 @@ describe('DeprecationFactory', () => { const processed = renameFromRoot('myplugin.deprecated', 'myplugin.renamed')( rawConfig, 'does-not-matter', - logger + addDeprecation ); expect(processed).toEqual({ myplugin: { @@ -149,9 +176,18 @@ describe('DeprecationFactory', () => { property: 'value', }, }); - expect(deprecationMessages).toMatchInlineSnapshot(` + expect(addDeprecation.mock.calls).toMatchInlineSnapshot(` Array [ - "\\"myplugin.deprecated\\" is deprecated and has been replaced by \\"myplugin.renamed\\"", + Array [ + Object { + "correctiveActions": Object { + "manualSteps": Array [ + "Replace \\"myplugin.deprecated\\" with \\"myplugin.renamed\\" in the Kibana config file, CLI flag, or environment variable (in Docker only).", + ], + }, + "message": "\\"myplugin.deprecated\\" is deprecated and has been replaced by \\"myplugin.renamed\\"", + }, + ], ] `); }); @@ -169,7 +205,7 @@ describe('DeprecationFactory', () => { const processed = renameFromRoot('oldplugin.deprecated', 'newplugin.renamed')( rawConfig, 'does-not-matter', - logger + addDeprecation ); expect(processed).toEqual({ oldplugin: { @@ -180,9 +216,18 @@ describe('DeprecationFactory', () => { property: 'value', }, }); - expect(deprecationMessages).toMatchInlineSnapshot(` + expect(addDeprecation.mock.calls).toMatchInlineSnapshot(` Array [ - "\\"oldplugin.deprecated\\" is deprecated and has been replaced by \\"newplugin.renamed\\"", + Array [ + Object { + "correctiveActions": Object { + "manualSteps": Array [ + "Replace \\"oldplugin.deprecated\\" with \\"newplugin.renamed\\" in the Kibana config file, CLI flag, or environment variable (in Docker only).", + ], + }, + "message": "\\"oldplugin.deprecated\\" is deprecated and has been replaced by \\"newplugin.renamed\\"", + }, + ], ] `); }); @@ -200,7 +245,7 @@ describe('DeprecationFactory', () => { const processed = renameFromRoot('myplugin.deprecated', 'myplugin.new')( rawConfig, 'does-not-matter', - logger + addDeprecation ); expect(processed).toEqual({ myplugin: { @@ -211,7 +256,7 @@ describe('DeprecationFactory', () => { property: 'value', }, }); - expect(deprecationMessages.length).toEqual(0); + expect(addDeprecation).toBeCalledTimes(0); }); it('remove the old property but does not overrides the new one if they both exist, and logs a specific message', () => { @@ -224,16 +269,27 @@ describe('DeprecationFactory', () => { const processed = renameFromRoot('myplugin.deprecated', 'myplugin.renamed')( rawConfig, 'does-not-matter', - logger + addDeprecation ); expect(processed).toEqual({ myplugin: { renamed: 'renamed', }, }); - expect(deprecationMessages).toMatchInlineSnapshot(` + + expect(addDeprecation.mock.calls).toMatchInlineSnapshot(` Array [ - "\\"myplugin.deprecated\\" is deprecated and has been replaced by \\"myplugin.renamed\\". However both key are present, ignoring \\"myplugin.deprecated\\"", + Array [ + Object { + "correctiveActions": Object { + "manualSteps": Array [ + "Make sure \\"myplugin.renamed\\" contains the correct value in the config file, CLI flag, or environment variable (in Docker only).", + "Remove \\"myplugin.deprecated\\" from the config.", + ], + }, + "message": "\\"myplugin.deprecated\\" is deprecated and has been replaced by \\"myplugin.renamed\\". However both key are present, ignoring \\"myplugin.deprecated\\"", + }, + ], ] `); }); @@ -250,7 +306,7 @@ describe('DeprecationFactory', () => { property: 'value', }, }; - const processed = unused('deprecated')(rawConfig, 'myplugin', logger); + const processed = unused('deprecated')(rawConfig, 'myplugin', addDeprecation); expect(processed).toEqual({ myplugin: { valid: 'valid', @@ -259,9 +315,18 @@ describe('DeprecationFactory', () => { property: 'value', }, }); - expect(deprecationMessages).toMatchInlineSnapshot(` + expect(addDeprecation.mock.calls).toMatchInlineSnapshot(` Array [ - "myplugin.deprecated is deprecated and is no longer used", + Array [ + Object { + "correctiveActions": Object { + "manualSteps": Array [ + "Remove \\"myplugin.deprecated\\" from the Kibana config file, CLI flag, or environment variable (in Docker only)", + ], + }, + "message": "myplugin.deprecated is deprecated and is no longer used", + }, + ], ] `); }); @@ -278,7 +343,7 @@ describe('DeprecationFactory', () => { property: 'value', }, }; - const processed = unused('section.deprecated')(rawConfig, 'myplugin', logger); + const processed = unused('section.deprecated')(rawConfig, 'myplugin', addDeprecation); expect(processed).toEqual({ myplugin: { valid: 'valid', @@ -288,9 +353,19 @@ describe('DeprecationFactory', () => { property: 'value', }, }); - expect(deprecationMessages).toMatchInlineSnapshot(` + + expect(addDeprecation.mock.calls).toMatchInlineSnapshot(` Array [ - "myplugin.section.deprecated is deprecated and is no longer used", + Array [ + Object { + "correctiveActions": Object { + "manualSteps": Array [ + "Remove \\"myplugin.section.deprecated\\" from the Kibana config file, CLI flag, or environment variable (in Docker only)", + ], + }, + "message": "myplugin.section.deprecated is deprecated and is no longer used", + }, + ], ] `); }); @@ -304,7 +379,7 @@ describe('DeprecationFactory', () => { property: 'value', }, }; - const processed = unused('deprecated')(rawConfig, 'myplugin', logger); + const processed = unused('deprecated')(rawConfig, 'myplugin', addDeprecation); expect(processed).toEqual({ myplugin: { valid: 'valid', @@ -313,7 +388,7 @@ describe('DeprecationFactory', () => { property: 'value', }, }); - expect(deprecationMessages.length).toEqual(0); + expect(addDeprecation).toBeCalledTimes(0); }); }); @@ -328,7 +403,11 @@ describe('DeprecationFactory', () => { property: 'value', }, }; - const processed = unusedFromRoot('myplugin.deprecated')(rawConfig, 'does-not-matter', logger); + const processed = unusedFromRoot('myplugin.deprecated')( + rawConfig, + 'does-not-matter', + addDeprecation + ); expect(processed).toEqual({ myplugin: { valid: 'valid', @@ -337,9 +416,19 @@ describe('DeprecationFactory', () => { property: 'value', }, }); - expect(deprecationMessages).toMatchInlineSnapshot(` + + expect(addDeprecation.mock.calls).toMatchInlineSnapshot(` Array [ - "myplugin.deprecated is deprecated and is no longer used", + Array [ + Object { + "correctiveActions": Object { + "manualSteps": Array [ + "Remove \\"myplugin.deprecated\\" from the Kibana config file, CLI flag, or environment variable (in Docker only)", + ], + }, + "message": "myplugin.deprecated is deprecated and is no longer used", + }, + ], ] `); }); @@ -353,7 +442,11 @@ describe('DeprecationFactory', () => { property: 'value', }, }; - const processed = unusedFromRoot('myplugin.deprecated')(rawConfig, 'does-not-matter', logger); + const processed = unusedFromRoot('myplugin.deprecated')( + rawConfig, + 'does-not-matter', + addDeprecation + ); expect(processed).toEqual({ myplugin: { valid: 'valid', @@ -362,7 +455,7 @@ describe('DeprecationFactory', () => { property: 'value', }, }); - expect(deprecationMessages.length).toEqual(0); + expect(addDeprecation).toBeCalledTimes(0); }); }); }); diff --git a/packages/kbn-config/src/deprecation/deprecation_factory.ts b/packages/kbn-config/src/deprecation/deprecation_factory.ts index 73196dc897a518..140846d86ae0b4 100644 --- a/packages/kbn-config/src/deprecation/deprecation_factory.ts +++ b/packages/kbn-config/src/deprecation/deprecation_factory.ts @@ -9,15 +9,20 @@ import { get } from 'lodash'; import { set } from '@elastic/safer-lodash-set'; import { unset } from '@kbn/std'; -import { ConfigDeprecation, ConfigDeprecationLogger, ConfigDeprecationFactory } from './types'; +import { + ConfigDeprecation, + AddConfigDeprecation, + ConfigDeprecationFactory, + DeprecatedConfigDetails, +} from './types'; const _rename = ( config: Record, rootPath: string, - log: ConfigDeprecationLogger, + addDeprecation: AddConfigDeprecation, oldKey: string, newKey: string, - silent?: boolean + details?: Partial ) => { const fullOldPath = getPath(rootPath, oldKey); const oldValue = get(config, fullOldPath); @@ -32,48 +37,80 @@ const _rename = ( if (newValue === undefined) { set(config, fullNewPath, oldValue); - if (!silent) { - log(`"${fullOldPath}" is deprecated and has been replaced by "${fullNewPath}"`); - } + addDeprecation({ + message: `"${fullOldPath}" is deprecated and has been replaced by "${fullNewPath}"`, + correctiveActions: { + manualSteps: [ + `Replace "${fullOldPath}" with "${fullNewPath}" in the Kibana config file, CLI flag, or environment variable (in Docker only).`, + ], + }, + ...details, + }); } else { - if (!silent) { - log( - `"${fullOldPath}" is deprecated and has been replaced by "${fullNewPath}". However both key are present, ignoring "${fullOldPath}"` - ); - } + addDeprecation({ + message: `"${fullOldPath}" is deprecated and has been replaced by "${fullNewPath}". However both key are present, ignoring "${fullOldPath}"`, + correctiveActions: { + manualSteps: [ + `Make sure "${fullNewPath}" contains the correct value in the config file, CLI flag, or environment variable (in Docker only).`, + `Remove "${fullOldPath}" from the config.`, + ], + }, + ...details, + }); } + return config; }; const _unused = ( config: Record, rootPath: string, - log: ConfigDeprecationLogger, - unusedKey: string + addDeprecation: AddConfigDeprecation, + unusedKey: string, + details?: Partial ) => { const fullPath = getPath(rootPath, unusedKey); if (get(config, fullPath) === undefined) { return config; } unset(config, fullPath); - log(`${fullPath} is deprecated and is no longer used`); + addDeprecation({ + message: `${fullPath} is deprecated and is no longer used`, + correctiveActions: { + manualSteps: [ + `Remove "${fullPath}" from the Kibana config file, CLI flag, or environment variable (in Docker only)`, + ], + }, + ...details, + }); return config; }; -const rename = (oldKey: string, newKey: string): ConfigDeprecation => (config, rootPath, log) => - _rename(config, rootPath, log, oldKey, newKey); +const rename = ( + oldKey: string, + newKey: string, + details?: Partial +): ConfigDeprecation => (config, rootPath, addDeprecation) => + _rename(config, rootPath, addDeprecation, oldKey, newKey, details); -const renameFromRoot = (oldKey: string, newKey: string, silent?: boolean): ConfigDeprecation => ( - config, - rootPath, - log -) => _rename(config, '', log, oldKey, newKey, silent); +const renameFromRoot = ( + oldKey: string, + newKey: string, + details?: Partial +): ConfigDeprecation => (config, rootPath, addDeprecation) => + _rename(config, '', addDeprecation, oldKey, newKey, details); -const unused = (unusedKey: string): ConfigDeprecation => (config, rootPath, log) => - _unused(config, rootPath, log, unusedKey); +const unused = ( + unusedKey: string, + details?: Partial +): ConfigDeprecation => (config, rootPath, addDeprecation) => + _unused(config, rootPath, addDeprecation, unusedKey, details); -const unusedFromRoot = (unusedKey: string): ConfigDeprecation => (config, rootPath, log) => - _unused(config, '', log, unusedKey); +const unusedFromRoot = ( + unusedKey: string, + details?: Partial +): ConfigDeprecation => (config, rootPath, addDeprecation) => + _unused(config, '', addDeprecation, unusedKey, details); const getPath = (rootPath: string, subPath: string) => rootPath !== '' ? `${rootPath}.${subPath}` : subPath; diff --git a/packages/kbn-config/src/deprecation/index.ts b/packages/kbn-config/src/deprecation/index.ts index 6fe1a53efecbcc..3286acca9e584a 100644 --- a/packages/kbn-config/src/deprecation/index.ts +++ b/packages/kbn-config/src/deprecation/index.ts @@ -6,12 +6,13 @@ * Side Public License, v 1. */ -export { +export type { ConfigDeprecation, ConfigDeprecationWithContext, - ConfigDeprecationLogger, ConfigDeprecationFactory, + AddConfigDeprecation, ConfigDeprecationProvider, + DeprecatedConfigDetails, } from './types'; export { configDeprecationFactory } from './deprecation_factory'; export { applyDeprecations } from './apply_deprecations'; diff --git a/packages/kbn-config/src/deprecation/types.ts b/packages/kbn-config/src/deprecation/types.ts index 6e1816867abcfa..3b1d004d7ec761 100644 --- a/packages/kbn-config/src/deprecation/types.ts +++ b/packages/kbn-config/src/deprecation/types.ts @@ -7,11 +7,33 @@ */ /** - * Logger interface used when invoking a {@link ConfigDeprecation} + * Config deprecation hook used when invoking a {@link ConfigDeprecation} * * @public */ -export type ConfigDeprecationLogger = (message: string) => void; +export type AddConfigDeprecation = (details: DeprecatedConfigDetails) => void; + +/** + * Deprecated Config Details + * + * @public + */ +export interface DeprecatedConfigDetails { + /* The message to be displayed for the deprecation. */ + message: string; + /* (optional) set false to prevent the config service from logging the deprecation message. */ + silent?: boolean; + /* (optional) link to the documentation for more details on the deprecation. */ + documentationUrl?: string; + /* (optional) corrective action needed to fix this deprecation. */ + correctiveActions?: { + /** + * Specify a list of manual steps our users need to follow + * to fix the deprecation before upgrade. + */ + manualSteps: string[]; + }; +} /** * Configuration deprecation returned from {@link ConfigDeprecationProvider} that handles a single deprecation from the configuration. @@ -25,7 +47,7 @@ export type ConfigDeprecationLogger = (message: string) => void; export type ConfigDeprecation = ( config: Record, fromPath: string, - logger: ConfigDeprecationLogger + addDeprecation: AddConfigDeprecation ) => Record; /** @@ -62,6 +84,7 @@ export type ConfigDeprecationProvider = (factory: ConfigDeprecationFactory) => C * * @public */ + export interface ConfigDeprecationFactory { /** * Rename a configuration property from inside a plugin's configuration path. @@ -75,7 +98,11 @@ export interface ConfigDeprecationFactory { * ] * ``` */ - rename(oldKey: string, newKey: string): ConfigDeprecation; + rename( + oldKey: string, + newKey: string, + details?: Partial + ): ConfigDeprecation; /** * Rename a configuration property from the root configuration. * Will log a deprecation warning if the oldKey was found and deprecation applied. @@ -91,7 +118,11 @@ export interface ConfigDeprecationFactory { * ] * ``` */ - renameFromRoot(oldKey: string, newKey: string, silent?: boolean): ConfigDeprecation; + renameFromRoot( + oldKey: string, + newKey: string, + details?: Partial + ): ConfigDeprecation; /** * Remove a configuration property from inside a plugin's configuration path. * Will log a deprecation warning if the unused key was found and deprecation applied. @@ -104,7 +135,7 @@ export interface ConfigDeprecationFactory { * ] * ``` */ - unused(unusedKey: string): ConfigDeprecation; + unused(unusedKey: string, details?: Partial): ConfigDeprecation; /** * Remove a configuration property from the root configuration. * Will log a deprecation warning if the unused key was found and deprecation applied. @@ -120,7 +151,7 @@ export interface ConfigDeprecationFactory { * ] * ``` */ - unusedFromRoot(unusedKey: string): ConfigDeprecation; + unusedFromRoot(unusedKey: string, details?: Partial): ConfigDeprecation; } /** @internal */ diff --git a/packages/kbn-config/src/index.ts b/packages/kbn-config/src/index.ts index 8b0bdb0befbfdb..a9ea8265a37682 100644 --- a/packages/kbn-config/src/index.ts +++ b/packages/kbn-config/src/index.ts @@ -6,16 +6,16 @@ * Side Public License, v 1. */ -export { - applyDeprecations, - ConfigDeprecation, +export type { ConfigDeprecationFactory, - configDeprecationFactory, - ConfigDeprecationLogger, + AddConfigDeprecation, ConfigDeprecationProvider, ConfigDeprecationWithContext, + ConfigDeprecation, } from './deprecation'; +export { applyDeprecations, configDeprecationFactory } from './deprecation'; + export { RawConfigurationProvider, RawConfigService, diff --git a/src/core/public/core_system.ts b/src/core/public/core_system.ts index 278bbe469e862d..b68a7ced118d25 100644 --- a/src/core/public/core_system.ts +++ b/src/core/public/core_system.ts @@ -28,6 +28,7 @@ import { DocLinksService } from './doc_links'; import { RenderingService } from './rendering'; import { SavedObjectsService } from './saved_objects'; import { IntegrationsService } from './integrations'; +import { DeprecationsService } from './deprecations'; import { CoreApp } from './core_app'; import type { InternalApplicationSetup, InternalApplicationStart } from './application/types'; @@ -82,7 +83,7 @@ export class CoreSystem { private readonly rendering: RenderingService; private readonly integrations: IntegrationsService; private readonly coreApp: CoreApp; - + private readonly deprecations: DeprecationsService; private readonly rootDomElement: HTMLElement; private readonly coreContext: CoreContext; private fatalErrorsSetup: FatalErrorsSetup | null = null; @@ -113,6 +114,7 @@ export class CoreSystem { this.rendering = new RenderingService(); this.application = new ApplicationService(); this.integrations = new IntegrationsService(); + this.deprecations = new DeprecationsService(); this.coreContext = { coreId: Symbol('core'), env: injectedMetadata.env }; this.plugins = new PluginsService(this.coreContext, injectedMetadata.uiPlugins); @@ -195,6 +197,7 @@ export class CoreSystem { injectedMetadata, notifications, }); + const deprecations = this.deprecations.start({ http }); this.coreApp.start({ application, http, notifications, uiSettings }); @@ -210,6 +213,7 @@ export class CoreSystem { overlays, uiSettings, fatalErrors, + deprecations, }; await this.plugins.start(core); @@ -252,6 +256,7 @@ export class CoreSystem { this.chrome.stop(); this.i18n.stop(); this.application.stop(); + this.deprecations.stop(); this.rootDomElement.textContent = ''; } } diff --git a/src/core/public/deprecations/deprecations_client.test.ts b/src/core/public/deprecations/deprecations_client.test.ts new file mode 100644 index 00000000000000..2f52f7b4af195b --- /dev/null +++ b/src/core/public/deprecations/deprecations_client.test.ts @@ -0,0 +1,187 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { httpServiceMock } from '../http/http_service.mock'; +import { DeprecationsClient } from './deprecations_client'; +import type { DomainDeprecationDetails } from '../../server/types'; + +describe('DeprecationsClient', () => { + const http = httpServiceMock.createSetupContract(); + const mockDeprecations = [ + { domainId: 'testPluginId-1' }, + { domainId: 'testPluginId-1' }, + { domainId: 'testPluginId-2' }, + ]; + + beforeEach(() => { + http.fetch.mockReset(); + http.fetch.mockResolvedValue({ deprecations: mockDeprecations }); + }); + + describe('getAllDeprecations', () => { + it('returns a list of deprecations', async () => { + const deprecationsClient = new DeprecationsClient({ http }); + const deprecations = await deprecationsClient.getAllDeprecations(); + expect(http.fetch).toBeCalledTimes(1); + expect(http.fetch).toBeCalledWith('/api/deprecations/', { + asSystemRequest: true, + }); + + expect(deprecations).toEqual(mockDeprecations); + }); + }); + + describe('getDeprecations', () => { + it('returns deprecations for a single domainId', async () => { + const deprecationsClient = new DeprecationsClient({ http }); + const deprecations = await deprecationsClient.getDeprecations('testPluginId-1'); + + expect(deprecations.length).toBe(2); + expect(deprecations).toEqual([ + { domainId: 'testPluginId-1' }, + { domainId: 'testPluginId-1' }, + ]); + }); + + it('returns [] if the domainId does not have any deprecations', async () => { + const deprecationsClient = new DeprecationsClient({ http }); + const deprecations = await deprecationsClient.getDeprecations('testPluginId-4'); + + expect(deprecations).toEqual([]); + }); + + it('calls the fetch api', async () => { + const deprecationsClient = new DeprecationsClient({ http }); + http.fetch.mockResolvedValueOnce({ + deprecations: [{ domainId: 'testPluginId-1' }, { domainId: 'testPluginId-1' }], + }); + http.fetch.mockResolvedValueOnce({ + deprecations: [{ domainId: 'testPluginId-2' }, { domainId: 'testPluginId-2' }], + }); + const results = [ + ...(await deprecationsClient.getDeprecations('testPluginId-1')), + ...(await deprecationsClient.getDeprecations('testPluginId-2')), + ]; + + expect(http.fetch).toBeCalledTimes(2); + expect(results).toEqual([ + { domainId: 'testPluginId-1' }, + { domainId: 'testPluginId-1' }, + { domainId: 'testPluginId-2' }, + { domainId: 'testPluginId-2' }, + ]); + }); + }); + + describe('isDeprecationResolvable', () => { + it('returns true if deprecation has correctiveActions.api', async () => { + const deprecationsClient = new DeprecationsClient({ http }); + const mockDeprecationDetails: DomainDeprecationDetails = { + domainId: 'testPluginId-1', + message: 'some-message', + level: 'warning', + correctiveActions: { + api: { + path: 'some-path', + method: 'POST', + }, + }, + }; + + const isResolvable = deprecationsClient.isDeprecationResolvable(mockDeprecationDetails); + + expect(isResolvable).toBe(true); + }); + + it('returns false if deprecation is missing correctiveActions.api', async () => { + const deprecationsClient = new DeprecationsClient({ http }); + const mockDeprecationDetails: DomainDeprecationDetails = { + domainId: 'testPluginId-1', + message: 'some-message', + level: 'warning', + correctiveActions: {}, + }; + + const isResolvable = deprecationsClient.isDeprecationResolvable(mockDeprecationDetails); + + expect(isResolvable).toBe(false); + }); + }); + + describe('resolveDeprecation', () => { + it('fails if deprecation is not resolvable', async () => { + const deprecationsClient = new DeprecationsClient({ http }); + const mockDeprecationDetails: DomainDeprecationDetails = { + domainId: 'testPluginId-1', + message: 'some-message', + level: 'warning', + correctiveActions: {}, + }; + const result = await deprecationsClient.resolveDeprecation(mockDeprecationDetails); + + expect(result).toEqual({ + status: 'fail', + reason: 'deprecation has no correctiveAction via api.', + }); + }); + + it('fetches the deprecation api', async () => { + const deprecationsClient = new DeprecationsClient({ http }); + const mockDeprecationDetails: DomainDeprecationDetails = { + domainId: 'testPluginId-1', + message: 'some-message', + level: 'warning', + correctiveActions: { + api: { + path: 'some-path', + method: 'POST', + body: { + extra_param: 123, + }, + }, + }, + }; + const result = await deprecationsClient.resolveDeprecation(mockDeprecationDetails); + + expect(http.fetch).toBeCalledTimes(1); + expect(http.fetch).toBeCalledWith({ + path: 'some-path', + method: 'POST', + asSystemRequest: true, + body: JSON.stringify({ + extra_param: 123, + deprecationDetails: { domainId: 'testPluginId-1' }, + }), + }); + expect(result).toEqual({ status: 'ok' }); + }); + + it('fails when fetch fails', async () => { + const deprecationsClient = new DeprecationsClient({ http }); + const mockResponse = 'Failed to fetch'; + const mockDeprecationDetails: DomainDeprecationDetails = { + domainId: 'testPluginId-1', + message: 'some-message', + level: 'warning', + correctiveActions: { + api: { + path: 'some-path', + method: 'POST', + body: { + extra_param: 123, + }, + }, + }, + }; + http.fetch.mockRejectedValue({ body: { message: mockResponse } }); + const result = await deprecationsClient.resolveDeprecation(mockDeprecationDetails); + + expect(result).toEqual({ status: 'fail', reason: mockResponse }); + }); + }); +}); diff --git a/src/core/public/deprecations/deprecations_client.ts b/src/core/public/deprecations/deprecations_client.ts new file mode 100644 index 00000000000000..e510ab1e79d172 --- /dev/null +++ b/src/core/public/deprecations/deprecations_client.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 + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { HttpStart } from '../http'; +import type { DomainDeprecationDetails, DeprecationsGetResponse } from '../../server/types'; + +/* @internal */ +export interface DeprecationsClientDeps { + http: Pick; +} + +/* @internal */ +export type ResolveDeprecationResponse = { status: 'ok' } | { status: 'fail'; reason: string }; + +export class DeprecationsClient { + private readonly http: Pick; + constructor({ http }: DeprecationsClientDeps) { + this.http = http; + } + + private fetchDeprecations = async (): Promise => { + const { deprecations } = await this.http.fetch('/api/deprecations/', { + asSystemRequest: true, + }); + + return deprecations; + }; + + public getAllDeprecations = async () => { + return await this.fetchDeprecations(); + }; + + public getDeprecations = async (domainId: string) => { + const deprecations = await this.fetchDeprecations(); + return deprecations.filter((deprecation) => deprecation.domainId === domainId); + }; + + public isDeprecationResolvable = (details: DomainDeprecationDetails) => { + return typeof details.correctiveActions.api === 'object'; + }; + + public resolveDeprecation = async ( + details: DomainDeprecationDetails + ): Promise => { + const { domainId, correctiveActions } = details; + // explicit check required for TS type guard + if (typeof correctiveActions.api !== 'object') { + return { + status: 'fail', + reason: 'deprecation has no correctiveAction via api.', + }; + } + + const { body, method, path } = correctiveActions.api; + try { + await this.http.fetch({ + path, + method, + asSystemRequest: true, + body: JSON.stringify({ + ...body, + deprecationDetails: { domainId }, + }), + }); + return { status: 'ok' }; + } catch (err) { + return { + status: 'fail', + reason: err.body.message, + }; + } + }; +} diff --git a/src/core/public/deprecations/deprecations_service.mock.ts b/src/core/public/deprecations/deprecations_service.mock.ts new file mode 100644 index 00000000000000..5bcd52982d5131 --- /dev/null +++ b/src/core/public/deprecations/deprecations_service.mock.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 + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { PublicMethodsOf } from '@kbn/utility-types'; +import { DeprecationsService } from './deprecations_service'; +import type { DeprecationsServiceStart } from './deprecations_service'; + +const createServiceMock = (): jest.Mocked => ({ + getAllDeprecations: jest.fn().mockResolvedValue([]), + getDeprecations: jest.fn().mockResolvedValue([]), + isDeprecationResolvable: jest.fn().mockReturnValue(false), + resolveDeprecation: jest.fn().mockResolvedValue({ status: 'ok', payload: {} }), +}); + +const createMock = () => { + const mocked: jest.Mocked> = { + setup: jest.fn(), + start: jest.fn(), + stop: jest.fn(), + }; + + mocked.setup.mockReturnValue(void 0); + mocked.start.mockReturnValue(createServiceMock()); + return mocked; +}; + +export const deprecationsServiceMock = { + create: createMock, + createSetupContract: () => void 0, + createStartContract: createServiceMock, +}; diff --git a/src/core/public/deprecations/deprecations_service.ts b/src/core/public/deprecations/deprecations_service.ts new file mode 100644 index 00000000000000..d06e0071d2bc72 --- /dev/null +++ b/src/core/public/deprecations/deprecations_service.ts @@ -0,0 +1,60 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { CoreService } from '../../types'; +import type { HttpStart } from '../http'; +import { DeprecationsClient, ResolveDeprecationResponse } from './deprecations_client'; +import type { DomainDeprecationDetails } from '../../server/types'; + +/** + * DeprecationsService provides methods to fetch domain deprecation details from + * the Kibana server. + * + * @public + */ +export interface DeprecationsServiceStart { + /** + * Grabs deprecations details for all domains. + */ + getAllDeprecations: () => Promise; + /** + * Grabs deprecations for a specific domain. + * + * @param {string} domainId + */ + getDeprecations: (domainId: string) => Promise; + /** + * Returns a boolean if the provided deprecation can be automatically resolvable. + * + * @param {DomainDeprecationDetails} details + */ + isDeprecationResolvable: (details: DomainDeprecationDetails) => boolean; + /** + * Calls the correctiveActions.api to automatically resolve the depprecation. + * + * @param {DomainDeprecationDetails} details + */ + resolveDeprecation: (details: DomainDeprecationDetails) => Promise; +} + +export class DeprecationsService implements CoreService { + public setup(): void {} + + public start({ http }: { http: HttpStart }): DeprecationsServiceStart { + const deprecationsClient = new DeprecationsClient({ http }); + + return { + getAllDeprecations: deprecationsClient.getAllDeprecations, + getDeprecations: deprecationsClient.getDeprecations, + isDeprecationResolvable: deprecationsClient.isDeprecationResolvable, + resolveDeprecation: deprecationsClient.resolveDeprecation, + }; + } + + public stop(): void {} +} diff --git a/src/core/public/deprecations/index.ts b/src/core/public/deprecations/index.ts new file mode 100644 index 00000000000000..092cbed613ac22 --- /dev/null +++ b/src/core/public/deprecations/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { DeprecationsService } from './deprecations_service'; +export type { DeprecationsServiceStart } from './deprecations_service'; +export type { ResolveDeprecationResponse } from './deprecations_client'; diff --git a/src/core/public/index.ts b/src/core/public/index.ts index c7b4c370eb6d7a..750f2e27dc9504 100644 --- a/src/core/public/index.ts +++ b/src/core/public/index.ts @@ -65,6 +65,7 @@ import { UiSettingsState, IUiSettingsClient } from './ui_settings'; import { ApplicationSetup, Capabilities, ApplicationStart } from './application'; import { DocLinksStart } from './doc_links'; import { SavedObjectsStart } from './saved_objects'; +import { DeprecationsServiceStart } from './deprecations'; export type { PackageInfo, EnvironmentMode, IExternalUrlPolicy } from '../server/types'; export type { CoreContext, CoreSystem } from './core_system'; @@ -184,6 +185,8 @@ export type { ErrorToastOptions, } from './notifications'; +export type { DeprecationsServiceStart, ResolveDeprecationResponse } from './deprecations'; + export type { MountPoint, UnmountCallback, PublicUiSettingsParams } from './types'; export { URL_MAX_LENGTH } from './core_app'; @@ -268,6 +271,8 @@ export interface CoreStart { uiSettings: IUiSettingsClient; /** {@link FatalErrorsStart} */ fatalErrors: FatalErrorsStart; + /** {@link DeprecationsServiceStart} */ + deprecations: DeprecationsServiceStart; /** * exposed temporarily until https://github.com/elastic/kibana/issues/41990 done * use *only* to retrieve config values. There is no way to set injected values diff --git a/src/core/public/mocks.ts b/src/core/public/mocks.ts index e47de84ea12b2f..bd7623beba651e 100644 --- a/src/core/public/mocks.ts +++ b/src/core/public/mocks.ts @@ -24,6 +24,7 @@ import { overlayServiceMock } from './overlays/overlay_service.mock'; import { uiSettingsServiceMock } from './ui_settings/ui_settings_service.mock'; import { savedObjectsServiceMock } from './saved_objects/saved_objects_service.mock'; import { injectedMetadataServiceMock } from './injected_metadata/injected_metadata_service.mock'; +import { deprecationsServiceMock } from './deprecations/deprecations_service.mock'; export { chromeServiceMock } from './chrome/chrome_service.mock'; export { docLinksServiceMock } from './doc_links/doc_links_service.mock'; @@ -37,6 +38,7 @@ export { uiSettingsServiceMock } from './ui_settings/ui_settings_service.mock'; export { savedObjectsServiceMock } from './saved_objects/saved_objects_service.mock'; export { scopedHistoryMock } from './application/scoped_history.mock'; export { applicationServiceMock } from './application/application_service.mock'; +export { deprecationsServiceMock } from './deprecations/deprecations_service.mock'; function createCoreSetupMock({ basePath = '', @@ -57,6 +59,7 @@ function createCoreSetupMock({ http: httpServiceMock.createSetupContract({ basePath }), notifications: notificationServiceMock.createSetupContract(), uiSettings: uiSettingsServiceMock.createSetupContract(), + deprecations: deprecationsServiceMock.createSetupContract(), injectedMetadata: { getInjectedVar: injectedMetadataServiceMock.createSetupContract().getInjectedVar, }, @@ -76,6 +79,7 @@ function createCoreStartMock({ basePath = '' } = {}) { overlays: overlayServiceMock.createStartContract(), uiSettings: uiSettingsServiceMock.createStartContract(), savedObjects: savedObjectsServiceMock.createStartContract(), + deprecations: deprecationsServiceMock.createStartContract(), injectedMetadata: { getInjectedVar: injectedMetadataServiceMock.createStartContract().getInjectedVar, }, diff --git a/src/core/public/plugins/plugin_context.ts b/src/core/public/plugins/plugin_context.ts index b59516fa121fbd..49c895aa80fc40 100644 --- a/src/core/public/plugins/plugin_context.ts +++ b/src/core/public/plugins/plugin_context.ts @@ -139,5 +139,6 @@ export function createPluginStartContext< getInjectedVar: deps.injectedMetadata.getInjectedVar, }, fatalErrors: deps.fatalErrors, + deprecations: deps.deprecations, }; } diff --git a/src/core/public/plugins/plugins_service.test.ts b/src/core/public/plugins/plugins_service.test.ts index e70b78f237d757..d7114f14e2f003 100644 --- a/src/core/public/plugins/plugins_service.test.ts +++ b/src/core/public/plugins/plugins_service.test.ts @@ -34,6 +34,7 @@ import { httpServiceMock } from '../http/http_service.mock'; import { CoreSetup, CoreStart, PluginInitializerContext } from '..'; import { docLinksServiceMock } from '../doc_links/doc_links_service.mock'; import { savedObjectsServiceMock } from '../saved_objects/saved_objects_service.mock'; +import { deprecationsServiceMock } from '../deprecations/deprecations_service.mock'; export let mockPluginInitializers: Map; @@ -101,6 +102,7 @@ describe('PluginsService', () => { uiSettings: uiSettingsServiceMock.createStartContract(), savedObjects: savedObjectsServiceMock.createStartContract(), fatalErrors: fatalErrorsServiceMock.createStartContract(), + deprecations: deprecationsServiceMock.createStartContract(), }; mockStartContext = { ...mockStartDeps, diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index 5a5ae253bac7f7..0a1c7a9b0fa360 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -432,6 +432,8 @@ export interface CoreStart { // (undocumented) chrome: ChromeStart; // (undocumented) + deprecations: DeprecationsServiceStart; + // (undocumented) docLinks: DocLinksStart; // (undocumented) fatalErrors: FatalErrorsStart; @@ -472,6 +474,15 @@ export class CoreSystem { // @internal (undocumented) export const DEFAULT_APP_CATEGORIES: Record; +// @public +export interface DeprecationsServiceStart { + // Warning: (ae-forgotten-export) The symbol "DomainDeprecationDetails" needs to be exported by the entry point index.d.ts + getAllDeprecations: () => Promise; + getDeprecations: (domainId: string) => Promise; + isDeprecationResolvable: (details: DomainDeprecationDetails) => boolean; + resolveDeprecation: (details: DomainDeprecationDetails) => Promise; +} + // @public (undocumented) export interface DocLinksStart { // (undocumented) @@ -1075,6 +1086,16 @@ export type PublicAppSearchDeepLinkInfo = Omit; +// Warning: (ae-missing-release-tag) "ResolveDeprecationResponse" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type ResolveDeprecationResponse = { + status: 'ok'; +} | { + status: 'fail'; + reason: string; +}; + // Warning: (ae-missing-release-tag) "SavedObject" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -1586,6 +1607,6 @@ export interface UserProvidedValues { // Warnings were encountered during analysis: // -// src/core/public/core_system.ts:164:21 - (ae-forgotten-export) The symbol "InternalApplicationStart" needs to be exported by the entry point index.d.ts +// src/core/public/core_system.ts:166:21 - (ae-forgotten-export) The symbol "InternalApplicationStart" needs to be exported by the entry point index.d.ts ``` diff --git a/src/core/server/config/deprecation/core_deprecations.test.ts b/src/core/server/config/deprecation/core_deprecations.test.ts index b6b3ab5b8facee..e3c236405a5961 100644 --- a/src/core/server/config/deprecation/core_deprecations.test.ts +++ b/src/core/server/config/deprecation/core_deprecations.test.ts @@ -20,7 +20,7 @@ const applyCoreDeprecations = (settings: Record = {}) => { deprecation, path: '', })), - (msg) => deprecationMessages.push(msg) + () => ({ message }) => deprecationMessages.push(message) ); return { messages: deprecationMessages, @@ -305,7 +305,7 @@ describe('core deprecations', () => { }); expect(messages).toMatchInlineSnapshot(` Array [ - "\\"logging.dest\\" has been deprecated and will be removed in 8.0. To set the destination moving forward, you can use the \\"console\\" appender in your logging configuration or define a custom one. For more details, see https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx.", + "\\"logging.dest\\" has been deprecated and will be removed in 8.0. To set the destination moving forward, you can use the \\"console\\" appender in your logging configuration or define a custom one. For more details, see https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx", ] `); }); @@ -315,7 +315,7 @@ describe('core deprecations', () => { }); expect(messages).toMatchInlineSnapshot(` Array [ - "\\"logging.dest\\" has been deprecated and will be removed in 8.0. To set the destination moving forward, you can use the \\"console\\" appender in your logging configuration or define a custom one. For more details, see https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx.", + "\\"logging.dest\\" has been deprecated and will be removed in 8.0. To set the destination moving forward, you can use the \\"console\\" appender in your logging configuration or define a custom one. For more details, see https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx", ] `); }); @@ -361,7 +361,7 @@ describe('core deprecations', () => { }); expect(messages).toMatchInlineSnapshot(` Array [ - "\\"logging.json\\" has been deprecated and will be removed in 8.0. To specify log message format moving forward, you can configure the \\"appender.layout\\" property for every custom appender in your logging configuration. There is currently no default layout for custom appenders and each one must be declared explicitly. For more details, see https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx.", + "\\"logging.json\\" has been deprecated and will be removed in 8.0. To specify log message format moving forward, you can configure the \\"appender.layout\\" property for every custom appender in your logging configuration. There is currently no default layout for custom appenders and each one must be declared explicitly. For more details, see https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx", ] `); }); @@ -446,7 +446,7 @@ describe('core deprecations', () => { }); expect(messages).toMatchInlineSnapshot(` Array [ - "\\"logging.filter\\" has been deprecated and will be removed in 8.0. ", + "\\"logging.filter\\" has been deprecated and will be removed in 8.0.", ] `); }); @@ -457,7 +457,7 @@ describe('core deprecations', () => { }); expect(messages).toMatchInlineSnapshot(` Array [ - "\\"logging.filter\\" has been deprecated and will be removed in 8.0. ", + "\\"logging.filter\\" has been deprecated and will be removed in 8.0.", ] `); }); diff --git a/src/core/server/config/deprecation/core_deprecations.ts b/src/core/server/config/deprecation/core_deprecations.ts index 565b957b2a8e1e..2e77374e3068ab 100644 --- a/src/core/server/config/deprecation/core_deprecations.ts +++ b/src/core/server/config/deprecation/core_deprecations.ts @@ -9,40 +9,43 @@ import { has, get } from 'lodash'; import { ConfigDeprecationProvider, ConfigDeprecation } from '@kbn/config'; -const configPathDeprecation: ConfigDeprecation = (settings, fromPath, log) => { +const configPathDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => { if (has(process.env, 'CONFIG_PATH')) { - log( - `Environment variable CONFIG_PATH is deprecated. It has been replaced with KBN_PATH_CONF pointing to a config folder` - ); + addDeprecation({ + message: `Environment variable CONFIG_PATH is deprecated. It has been replaced with KBN_PATH_CONF pointing to a config folder`, + }); } return settings; }; -const dataPathDeprecation: ConfigDeprecation = (settings, fromPath, log) => { +const dataPathDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => { if (has(process.env, 'DATA_PATH')) { - log( - `Environment variable "DATA_PATH" will be removed. It has been replaced with kibana.yml setting "path.data"` - ); + addDeprecation({ + message: `Environment variable "DATA_PATH" will be removed. It has been replaced with kibana.yml setting "path.data"`, + }); } return settings; }; -const rewriteBasePathDeprecation: ConfigDeprecation = (settings, fromPath, log) => { +const rewriteBasePathDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => { if (has(settings, 'server.basePath') && !has(settings, 'server.rewriteBasePath')) { - log( - 'You should set server.basePath along with server.rewriteBasePath. Starting in 7.0, Kibana ' + + addDeprecation({ + message: + 'You should set server.basePath along with server.rewriteBasePath. Starting in 7.0, Kibana ' + 'will expect that all requests start with server.basePath rather than expecting you to rewrite ' + 'the requests in your reverse proxy. Set server.rewriteBasePath to false to preserve the ' + - 'current behavior and silence this warning.' - ); + 'current behavior and silence this warning.', + }); } return settings; }; -const rewriteCorsSettings: ConfigDeprecation = (settings, fromPath, log) => { +const rewriteCorsSettings: ConfigDeprecation = (settings, fromPath, addDeprecation) => { const corsSettings = get(settings, 'server.cors'); if (typeof get(settings, 'server.cors') === 'boolean') { - log('"server.cors" is deprecated and has been replaced by "server.cors.enabled"'); + addDeprecation({ + message: '"server.cors" is deprecated and has been replaced by "server.cors.enabled"', + }); settings.server.cors = { enabled: corsSettings, }; @@ -50,7 +53,7 @@ const rewriteCorsSettings: ConfigDeprecation = (settings, fromPath, log) => { return settings; }; -const cspRulesDeprecation: ConfigDeprecation = (settings, fromPath, log) => { +const cspRulesDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => { const NONCE_STRING = `{nonce}`; // Policies that should include the 'self' source const SELF_POLICIES = Object.freeze(['script-src', 'style-src']); @@ -67,7 +70,9 @@ const cspRulesDeprecation: ConfigDeprecation = (settings, fromPath, log) => { settings.csp.rules = [...parsed].map(([policy, sourceList]) => { if (sourceList.find((source) => source.includes(NONCE_STRING))) { - log(`csp.rules no longer supports the {nonce} syntax. Replacing with 'self' in ${policy}`); + addDeprecation({ + message: `csp.rules no longer supports the {nonce} syntax. Replacing with 'self' in ${policy}`, + }); sourceList = sourceList.filter((source) => !source.includes(NONCE_STRING)); // Add 'self' if not present @@ -80,7 +85,9 @@ const cspRulesDeprecation: ConfigDeprecation = (settings, fromPath, log) => { SELF_POLICIES.includes(policy) && !sourceList.find((source) => source.includes(SELF_STRING)) ) { - log(`csp.rules must contain the 'self' source. Automatically adding to ${policy}.`); + addDeprecation({ + message: `csp.rules must contain the 'self' source. Automatically adding to ${policy}.`, + }); sourceList.push(SELF_STRING); } @@ -91,149 +98,191 @@ const cspRulesDeprecation: ConfigDeprecation = (settings, fromPath, log) => { return settings; }; -const mapManifestServiceUrlDeprecation: ConfigDeprecation = (settings, fromPath, log) => { +const mapManifestServiceUrlDeprecation: ConfigDeprecation = ( + settings, + fromPath, + addDeprecation +) => { if (has(settings, 'map.manifestServiceUrl')) { - log( - 'You should no longer use the map.manifestServiceUrl setting in kibana.yml to configure the location ' + + addDeprecation({ + message: + 'You should no longer use the map.manifestServiceUrl setting in kibana.yml to configure the location ' + 'of the Elastic Maps Service settings. These settings have moved to the "map.emsTileApiUrl" and ' + '"map.emsFileApiUrl" settings instead. These settings are for development use only and should not be ' + - 'modified for use in production environments.' - ); + 'modified for use in production environments.', + }); } return settings; }; -const opsLoggingEventDeprecation: ConfigDeprecation = (settings, fromPath, log) => { +const opsLoggingEventDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => { if (has(settings, 'logging.events.ops')) { - log( - '"logging.events.ops" has been deprecated and will be removed ' + + addDeprecation({ + documentationUrl: + 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx#loggingevents', + message: + '"logging.events.ops" has been deprecated and will be removed ' + 'in 8.0. To access ops data moving forward, please enable debug logs for the ' + '"metrics.ops" context in your logging configuration. For more details, see ' + - 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx' - ); + 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx', + }); } return settings; }; -const requestLoggingEventDeprecation: ConfigDeprecation = (settings, fromPath, log) => { +const requestLoggingEventDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => { if (has(settings, 'logging.events.request') || has(settings, 'logging.events.response')) { - log( - '"logging.events.request" and "logging.events.response" have been deprecated and will be removed ' + + addDeprecation({ + documentationUrl: + 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx#loggingevents', + message: + '"logging.events.request" and "logging.events.response" have been deprecated and will be removed ' + 'in 8.0. To access request and/or response data moving forward, please enable debug logs for the ' + '"http.server.response" context in your logging configuration. For more details, see ' + - 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx' - ); + 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx', + }); } return settings; }; -const timezoneLoggingDeprecation: ConfigDeprecation = (settings, fromPath, log) => { +const timezoneLoggingDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => { if (has(settings, 'logging.timezone')) { - log( - '"logging.timezone" has been deprecated and will be removed ' + + addDeprecation({ + documentationUrl: + 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx#loggingtimezone', + message: + '"logging.timezone" has been deprecated and will be removed ' + 'in 8.0. To set the timezone moving forward, please add a timezone date modifier to the log pattern ' + 'in your logging configuration. For more details, see ' + - 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx' - ); + 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx', + }); } return settings; }; -const destLoggingDeprecation: ConfigDeprecation = (settings, fromPath, log) => { +const destLoggingDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => { if (has(settings, 'logging.dest')) { - log( - '"logging.dest" has been deprecated and will be removed ' + + addDeprecation({ + documentationUrl: + 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx#loggingdest', + message: + '"logging.dest" has been deprecated and will be removed ' + 'in 8.0. To set the destination moving forward, you can use the "console" appender ' + 'in your logging configuration or define a custom one. For more details, see ' + - 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx.' - ); + 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx', + }); } return settings; }; -const quietLoggingDeprecation: ConfigDeprecation = (settings, fromPath, log) => { +const quietLoggingDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => { if (has(settings, 'logging.quiet')) { - log( - '"logging.quiet" has been deprecated and will be removed ' + - 'in 8.0. Moving forward, you can use "logging.root.level:error" in your logging configuration. ' - ); + addDeprecation({ + documentationUrl: + 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx#loggingquiet', + message: + '"logging.quiet" has been deprecated and will be removed ' + + 'in 8.0. Moving forward, you can use "logging.root.level:error" in your logging configuration. ', + }); } return settings; }; -const silentLoggingDeprecation: ConfigDeprecation = (settings, fromPath, log) => { +const silentLoggingDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => { if (has(settings, 'logging.silent')) { - log( - '"logging.silent" has been deprecated and will be removed ' + - 'in 8.0. Moving forward, you can use "logging.root.level:off" in your logging configuration. ' - ); + addDeprecation({ + documentationUrl: + 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx#loggingsilent', + message: + '"logging.silent" has been deprecated and will be removed ' + + 'in 8.0. Moving forward, you can use "logging.root.level:off" in your logging configuration. ', + }); } return settings; }; -const verboseLoggingDeprecation: ConfigDeprecation = (settings, fromPath, log) => { +const verboseLoggingDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => { if (has(settings, 'logging.verbose')) { - log( - '"logging.verbose" has been deprecated and will be removed ' + - 'in 8.0. Moving forward, you can use "logging.root.level:all" in your logging configuration. ' - ); + addDeprecation({ + documentationUrl: + 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx#loggingverbose', + message: + '"logging.verbose" has been deprecated and will be removed ' + + 'in 8.0. Moving forward, you can use "logging.root.level:all" in your logging configuration. ', + }); } return settings; }; -const jsonLoggingDeprecation: ConfigDeprecation = (settings, fromPath, log) => { +const jsonLoggingDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => { // We silence the deprecation warning when running in development mode because // the dev CLI code in src/dev/cli_dev_mode/using_server_process.ts manually // specifies `--logging.json=false`. Since it's executed in a child process, the // ` legacyLoggingConfigSchema` returns `true` for the TTY check on `process.stdout.isTTY` if (has(settings, 'logging.json') && settings.env !== 'development') { - log( - '"logging.json" has been deprecated and will be removed ' + + addDeprecation({ + documentationUrl: + 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx', + message: + '"logging.json" has been deprecated and will be removed ' + 'in 8.0. To specify log message format moving forward, ' + 'you can configure the "appender.layout" property for every custom appender in your logging configuration. ' + 'There is currently no default layout for custom appenders and each one must be declared explicitly. ' + 'For more details, see ' + - 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx.' - ); + 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx', + }); } return settings; }; -const logRotateDeprecation: ConfigDeprecation = (settings, fromPath, log) => { +const logRotateDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => { if (has(settings, 'logging.rotate')) { - log( - '"logging.rotate" and sub-options have been deprecated and will be removed in 8.0. ' + + addDeprecation({ + documentationUrl: + 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx#rolling-file-appender', + message: + '"logging.rotate" and sub-options have been deprecated and will be removed in 8.0. ' + 'Moving forward, you can enable log rotation using the "rolling-file" appender for a logger ' + 'in your logging configuration. For more details, see ' + - 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx#rolling-file-appender' - ); + 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx#rolling-file-appender', + }); } return settings; }; -const logEventsLogDeprecation: ConfigDeprecation = (settings, fromPath, log) => { +const logEventsLogDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => { if (has(settings, 'logging.events.log')) { - log( - '"logging.events.log" has been deprecated and will be removed ' + - 'in 8.0. Moving forward, log levels can be customized on a per-logger basis using the new logging configuration. ' - ); + addDeprecation({ + documentationUrl: + 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx#loggingevents', + message: + '"logging.events.log" has been deprecated and will be removed ' + + 'in 8.0. Moving forward, log levels can be customized on a per-logger basis using the new logging configuration. ', + }); } return settings; }; -const logEventsErrorDeprecation: ConfigDeprecation = (settings, fromPath, log) => { +const logEventsErrorDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => { if (has(settings, 'logging.events.error')) { - log( - '"logging.events.error" has been deprecated and will be removed ' + - 'in 8.0. Moving forward, you can use "logging.root.level: error" in your logging configuration. ' - ); + addDeprecation({ + documentationUrl: + 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx#loggingevents', + message: + '"logging.events.error" has been deprecated and will be removed ' + + 'in 8.0. Moving forward, you can use "logging.root.level: error" in your logging configuration. ', + }); } return settings; }; -const logFilterDeprecation: ConfigDeprecation = (settings, fromPath, log) => { +const logFilterDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => { if (has(settings, 'logging.filter')) { - log('"logging.filter" has been deprecated and will be removed ' + 'in 8.0. '); + addDeprecation({ + documentationUrl: + 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx#loggingfilter', + message: '"logging.filter" has been deprecated and will be removed in 8.0.', + }); } return settings; }; diff --git a/src/core/server/config/index.ts b/src/core/server/config/index.ts index bf2ce16e869b7c..b1086d4470335f 100644 --- a/src/core/server/config/index.ts +++ b/src/core/server/config/index.ts @@ -24,7 +24,7 @@ export type { ConfigPath, CliArgs, ConfigDeprecation, - ConfigDeprecationLogger, + AddConfigDeprecation, ConfigDeprecationProvider, ConfigDeprecationFactory, EnvironmentMode, diff --git a/src/core/server/deprecations/deprecations_factory.test.ts b/src/core/server/deprecations/deprecations_factory.test.ts new file mode 100644 index 00000000000000..469451b0020c02 --- /dev/null +++ b/src/core/server/deprecations/deprecations_factory.test.ts @@ -0,0 +1,248 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { GetDeprecationsContext } from './types'; +import { DeprecationsFactory } from './deprecations_factory'; +import { loggerMock } from '../logging/logger.mock'; + +describe('DeprecationsFactory', () => { + const logger = loggerMock.create(); + beforeEach(() => { + loggerMock.clear(logger); + }); + + describe('getRegistry', () => { + const domainId = 'test-plugin'; + + it('creates a registry for a domainId', async () => { + const deprecationsFactory = new DeprecationsFactory({ logger }); + const registry = deprecationsFactory.getRegistry(domainId); + + expect(registry).toHaveProperty('registerDeprecations'); + expect(registry).toHaveProperty('getDeprecations'); + }); + + it('creates one registry for a domainId', async () => { + const deprecationsFactory = new DeprecationsFactory({ logger }); + const registry = deprecationsFactory.getRegistry(domainId); + const sameRegistry = deprecationsFactory.getRegistry(domainId); + + expect(registry).toStrictEqual(sameRegistry); + }); + + it('returns a registered registry', () => { + const deprecationsFactory = new DeprecationsFactory({ logger }); + const mockRegistry = 'mock-reg'; + const mockRegistries = { + set: jest.fn(), + get: jest.fn().mockReturnValue(mockRegistry), + }; + + // @ts-expect-error + deprecationsFactory.registries = mockRegistries; + const result = deprecationsFactory.getRegistry(domainId); + + expect(mockRegistries.get).toBeCalledTimes(1); + expect(mockRegistries.get).toBeCalledWith(domainId); + expect(mockRegistries.set).toBeCalledTimes(0); + expect(result).toStrictEqual(mockRegistry); + }); + }); + + describe('getAllDeprecations', () => { + const mockDependencies = ({ + esClient: jest.fn(), + savedObjectsClient: jest.fn(), + } as unknown) as GetDeprecationsContext; + + it('returns a flattened array of deprecations', async () => { + const deprecationsFactory = new DeprecationsFactory({ logger }); + const mockPluginDeprecationsInfo = [ + { + message: 'mockPlugin message', + level: 'critical', + correctiveActions: { + manualSteps: ['mockPlugin step 1', 'mockPlugin step 2'], + }, + }, + { + message: 'hello there!', + level: 'warning', + correctiveActions: { + manualSteps: ['mockPlugin step a', 'mockPlugin step b'], + }, + }, + ]; + const anotherMockPluginDeprecationsInfo = [ + { + message: 'anotherMockPlugin message', + level: 'critical', + correctiveActions: { + manualSteps: ['anotherMockPlugin step 1', 'anotherMockPlugin step 2'], + }, + }, + ]; + + const mockPluginRegistry = deprecationsFactory.getRegistry('mockPlugin'); + const anotherMockPluginRegistry = deprecationsFactory.getRegistry('anotherMockPlugin'); + mockPluginRegistry.registerDeprecations({ + getDeprecations: jest.fn().mockResolvedValue(mockPluginDeprecationsInfo), + }); + anotherMockPluginRegistry.registerDeprecations({ + getDeprecations: jest.fn().mockResolvedValue(anotherMockPluginDeprecationsInfo), + }); + + const derpecations = await deprecationsFactory.getAllDeprecations(mockDependencies); + expect(derpecations).toStrictEqual( + [ + mockPluginDeprecationsInfo.map((info) => ({ ...info, domainId: 'mockPlugin' })), + anotherMockPluginDeprecationsInfo.map((info) => ({ + ...info, + domainId: 'anotherMockPlugin', + })), + ].flat() + ); + }); + + it(`returns a failure message for failed getDeprecations functions`, async () => { + const deprecationsFactory = new DeprecationsFactory({ logger }); + const domainId = 'mockPlugin'; + const mockError = new Error(); + + const deprecationsRegistry = deprecationsFactory.getRegistry(domainId); + deprecationsRegistry.registerDeprecations({ + getDeprecations: jest.fn().mockRejectedValue(mockError), + }); + const derpecations = await deprecationsFactory.getAllDeprecations(mockDependencies); + expect(logger.warn).toBeCalledTimes(1); + expect(logger.warn).toBeCalledWith( + `Failed to get deprecations info for plugin "${domainId}".`, + mockError + ); + expect(derpecations).toStrictEqual([ + { + domainId, + message: `Failed to get deprecations info for plugin "${domainId}".`, + level: 'fetch_error', + correctiveActions: { + manualSteps: ['Check Kibana server logs for error message.'], + }, + }, + ]); + }); + + it(`returns successful results even when some getDeprecations fail`, async () => { + const deprecationsFactory = new DeprecationsFactory({ logger }); + const mockPluginRegistry = deprecationsFactory.getRegistry('mockPlugin'); + const anotherMockPluginRegistry = deprecationsFactory.getRegistry('anotherMockPlugin'); + const mockError = new Error(); + const mockPluginDeprecationsInfo = [ + { + message: 'mockPlugin message', + level: 'critical', + correctiveActions: { + manualSteps: ['mockPlugin step 1', 'mockPlugin step 2'], + }, + }, + ]; + mockPluginRegistry.registerDeprecations({ + getDeprecations: jest.fn().mockResolvedValue(mockPluginDeprecationsInfo), + }); + anotherMockPluginRegistry.registerDeprecations({ + getDeprecations: jest.fn().mockRejectedValue(mockError), + }); + const derpecations = await deprecationsFactory.getAllDeprecations(mockDependencies); + + expect(logger.warn).toBeCalledTimes(1); + expect(logger.warn).toBeCalledWith( + `Failed to get deprecations info for plugin "anotherMockPlugin".`, + mockError + ); + expect(derpecations).toStrictEqual([ + ...mockPluginDeprecationsInfo.map((info) => ({ ...info, domainId: 'mockPlugin' })), + { + domainId: 'anotherMockPlugin', + message: `Failed to get deprecations info for plugin "anotherMockPlugin".`, + level: 'fetch_error', + correctiveActions: { + manualSteps: ['Check Kibana server logs for error message.'], + }, + }, + ]); + }); + }); + + describe('getDeprecations', () => { + const mockDependencies = ({ + esClient: jest.fn(), + savedObjectsClient: jest.fn(), + } as unknown) as GetDeprecationsContext; + + it('returns a flattened array of DeprecationInfo', async () => { + const deprecationsFactory = new DeprecationsFactory({ logger }); + const deprecationsRegistry = deprecationsFactory.getRegistry('mockPlugin'); + const deprecationsBody = [ + { + message: 'mockPlugin message', + level: 'critical', + correctiveActions: { + manualSteps: ['mockPlugin step 1', 'mockPlugin step 2'], + }, + }, + [ + { + message: 'hello there!', + level: 'warning', + correctiveActions: { + manualSteps: ['mockPlugin step a', 'mockPlugin step b'], + }, + }, + ], + ]; + + deprecationsRegistry.registerDeprecations({ + getDeprecations: jest.fn().mockResolvedValue(deprecationsBody), + }); + + const derpecations = await deprecationsFactory.getDeprecations( + 'mockPlugin', + mockDependencies + ); + expect(derpecations).toStrictEqual( + deprecationsBody.flat().map((body) => ({ ...body, domainId: 'mockPlugin' })) + ); + }); + + it('removes empty entries from the returned array', async () => { + const deprecationsFactory = new DeprecationsFactory({ logger }); + const deprecationsRegistry = deprecationsFactory.getRegistry('mockPlugin'); + const deprecationsBody = [ + { + message: 'mockPlugin message', + level: 'critical', + correctiveActions: { + manualSteps: ['mockPlugin step 1', 'mockPlugin step 2'], + }, + }, + [undefined], + undefined, + ]; + + deprecationsRegistry.registerDeprecations({ + getDeprecations: jest.fn().mockResolvedValue(deprecationsBody), + }); + + const derpecations = await deprecationsFactory.getDeprecations( + 'mockPlugin', + mockDependencies + ); + expect(derpecations).toHaveLength(1); + expect(derpecations).toStrictEqual([{ ...deprecationsBody[0], domainId: 'mockPlugin' }]); + }); + }); +}); diff --git a/src/core/server/deprecations/deprecations_factory.ts b/src/core/server/deprecations/deprecations_factory.ts new file mode 100644 index 00000000000000..3699c088e20f16 --- /dev/null +++ b/src/core/server/deprecations/deprecations_factory.ts @@ -0,0 +1,108 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { DeprecationsRegistry } from './deprecations_registry'; +import type { Logger } from '../logging'; +import type { + DomainDeprecationDetails, + DeprecationsDetails, + GetDeprecationsContext, +} from './types'; + +export interface DeprecationsFactoryDeps { + logger: Logger; +} + +export class DeprecationsFactory { + private readonly registries: Map = new Map(); + private readonly logger: Logger; + constructor({ logger }: DeprecationsFactoryDeps) { + this.logger = logger; + } + + public getRegistry = (domainId: string): DeprecationsRegistry => { + const existing = this.registries.get(domainId); + if (existing) { + return existing; + } + const registry = new DeprecationsRegistry(); + this.registries.set(domainId, registry); + return registry; + }; + + public getDeprecations = async ( + domainId: string, + dependencies: GetDeprecationsContext + ): Promise => { + const infoBody = await this.getDeprecationsBody(domainId, dependencies); + return this.createDeprecationInfo(domainId, infoBody).flat(); + }; + + public getAllDeprecations = async ( + dependencies: GetDeprecationsContext + ): Promise => { + const domainIds = [...this.registries.keys()]; + + const deprecationsInfo = await Promise.all( + domainIds.map(async (domainId) => { + const infoBody = await this.getDeprecationsBody(domainId, dependencies); + return this.createDeprecationInfo(domainId, infoBody); + }) + ); + + return deprecationsInfo.flat(); + }; + + private createDeprecationInfo = ( + domainId: string, + deprecationInfoBody: DeprecationsDetails[] + ): DomainDeprecationDetails[] => { + return deprecationInfoBody + .flat() + .filter(Boolean) + .map((pluginDeprecation) => ({ + ...pluginDeprecation, + domainId, + })); + }; + + private getDeprecationsBody = async ( + domainId: string, + dependencies: GetDeprecationsContext + ): Promise => { + const deprecationsRegistry = this.registries.get(domainId); + if (!deprecationsRegistry) { + return []; + } + try { + const settledResults = await deprecationsRegistry.getDeprecations(dependencies); + return settledResults.flatMap((settledResult) => { + if (settledResult.status === 'rejected') { + this.logger.warn( + `Failed to get deprecations info for plugin "${domainId}".`, + settledResult.reason + ); + return [ + { + message: `Failed to get deprecations info for plugin "${domainId}".`, + level: 'fetch_error', + correctiveActions: { + manualSteps: ['Check Kibana server logs for error message.'], + }, + }, + ]; + } + + return settledResult.value; + }); + } catch (err) { + this.logger.warn(`Failed to get deprecations info for plugin "${domainId}".`, err); + return []; + } + }; +} diff --git a/src/core/server/deprecations/deprecations_registry.test.ts b/src/core/server/deprecations/deprecations_registry.test.ts new file mode 100644 index 00000000000000..507677a5318618 --- /dev/null +++ b/src/core/server/deprecations/deprecations_registry.test.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 + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/* eslint-disable dot-notation */ +import { RegisterDeprecationsConfig, GetDeprecationsContext } from './types'; +import { DeprecationsRegistry } from './deprecations_registry'; + +describe('DeprecationsRegistry', () => { + describe('registerDeprecations', () => { + it('throws if getDeprecations is not a function', async () => { + const deprecationsRegistry = new DeprecationsRegistry(); + const deprecationsConfig = ({ + getDeprecations: null, + } as unknown) as RegisterDeprecationsConfig; + expect(() => deprecationsRegistry.registerDeprecations(deprecationsConfig)).toThrowError( + /getDeprecations must be a function/ + ); + }); + + it('registers deprecation context', () => { + const deprecationsRegistry = new DeprecationsRegistry(); + const getDeprecations = jest.fn(); + const deprecationsConfig = { getDeprecations }; + deprecationsRegistry.registerDeprecations(deprecationsConfig); + expect(deprecationsRegistry['deprecationContexts']).toStrictEqual([deprecationsConfig]); + }); + + it('allows registering multiple contexts', async () => { + const deprecationsRegistry = new DeprecationsRegistry(); + const deprecationsConfigA = { getDeprecations: jest.fn() }; + const deprecationsConfigB = { getDeprecations: jest.fn() }; + deprecationsRegistry.registerDeprecations(deprecationsConfigA); + deprecationsRegistry.registerDeprecations(deprecationsConfigB); + expect(deprecationsRegistry['deprecationContexts']).toStrictEqual([ + deprecationsConfigA, + deprecationsConfigB, + ]); + }); + }); + + describe('getDeprecations', () => { + it('returns all settled deprecations', async () => { + const deprecationsRegistry = new DeprecationsRegistry(); + const mockContext = ({} as unknown) as GetDeprecationsContext; + const mockError = new Error(); + const deprecationsConfigA = { getDeprecations: jest.fn().mockResolvedValue('hi') }; + const deprecationsConfigB = { getDeprecations: jest.fn().mockRejectedValue(mockError) }; + deprecationsRegistry.registerDeprecations(deprecationsConfigA); + deprecationsRegistry.registerDeprecations(deprecationsConfigB); + const deprecations = await deprecationsRegistry.getDeprecations(mockContext); + expect(deprecations).toStrictEqual([ + { + status: 'fulfilled', + value: 'hi', + }, + { + status: 'rejected', + reason: mockError, + }, + ]); + }); + + it('passes dependencies to registered getDeprecations function', async () => { + const deprecationsRegistry = new DeprecationsRegistry(); + const mockContext = ({} as unknown) as GetDeprecationsContext; + const deprecationsConfig = { getDeprecations: jest.fn().mockResolvedValue('hi') }; + deprecationsRegistry.registerDeprecations(deprecationsConfig); + const deprecations = await deprecationsRegistry.getDeprecations(mockContext); + expect(deprecations).toHaveLength(1); + expect(deprecationsConfig.getDeprecations).toBeCalledWith(mockContext); + }); + }); +}); diff --git a/src/core/server/deprecations/deprecations_registry.ts b/src/core/server/deprecations/deprecations_registry.ts new file mode 100644 index 00000000000000..f92d807514b821 --- /dev/null +++ b/src/core/server/deprecations/deprecations_registry.ts @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { DeprecationsDetails, RegisterDeprecationsConfig, GetDeprecationsContext } from './types'; + +export class DeprecationsRegistry { + private readonly deprecationContexts: RegisterDeprecationsConfig[] = []; + + public registerDeprecations = (deprecationContext: RegisterDeprecationsConfig) => { + if (typeof deprecationContext.getDeprecations !== 'function') { + throw new Error(`getDeprecations must be a function in registerDeprecations(context)`); + } + + this.deprecationContexts.push(deprecationContext); + }; + + public getDeprecations = async ( + dependencies: GetDeprecationsContext + ): Promise>> => { + return await Promise.allSettled( + this.deprecationContexts.map( + async (deprecationContext) => await deprecationContext.getDeprecations(dependencies) + ) + ); + }; +} diff --git a/src/core/server/deprecations/deprecations_service.mock.ts b/src/core/server/deprecations/deprecations_service.mock.ts new file mode 100644 index 00000000000000..c0febf90a489af --- /dev/null +++ b/src/core/server/deprecations/deprecations_service.mock.ts @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { PublicMethodsOf } from '@kbn/utility-types'; +import { + DeprecationsService, + InternalDeprecationsServiceSetup, + DeprecationsServiceSetup, +} from './deprecations_service'; +type DeprecationsServiceContract = PublicMethodsOf; + +const createSetupContractMock = () => { + const setupContract: jest.Mocked = { + registerDeprecations: jest.fn(), + }; + + return setupContract; +}; + +const createInternalSetupContractMock = () => { + const internalSetupContract: jest.Mocked = { + getRegistry: jest.fn(), + }; + + internalSetupContract.getRegistry.mockReturnValue(createSetupContractMock()); + return internalSetupContract; +}; + +const createDeprecationsServiceMock = () => { + const mocked: jest.Mocked = { + setup: jest.fn(), + start: jest.fn(), + stop: jest.fn(), + }; + + mocked.setup.mockReturnValue(createInternalSetupContractMock()); + return mocked; +}; + +export const deprecationsServiceMock = { + create: createDeprecationsServiceMock, + createInternalSetupContract: createInternalSetupContractMock, + createSetupContract: createSetupContractMock, +}; diff --git a/src/core/server/deprecations/deprecations_service.ts b/src/core/server/deprecations/deprecations_service.ts new file mode 100644 index 00000000000000..8eca1ba5790c55 --- /dev/null +++ b/src/core/server/deprecations/deprecations_service.ts @@ -0,0 +1,168 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { DeprecationsFactory } from './deprecations_factory'; +import { RegisterDeprecationsConfig } from './types'; +import { registerRoutes } from './routes'; + +import { CoreContext } from '../core_context'; +import { CoreUsageDataSetup } from '../core_usage_data'; +import { InternalElasticsearchServiceSetup } from '../elasticsearch'; +import { CoreService } from '../../types'; +import { InternalHttpServiceSetup } from '../http'; +import { Logger } from '../logging'; + +/** + * The deprecations service provides a way for the Kibana platform to communicate deprecated + * features and configs with its users. These deprecations are only communicated + * if the deployment is using these features. Allowing for a user tailored experience + * for upgrading the stack version. + * + * The Deprecation service is consumed by the upgrade assistant to assist with the upgrade + * experience. + * + * If a deprecated feature can be resolved without manual user intervention. + * Using correctiveActions.api allows the Upgrade Assistant to use this api to correct the + * deprecation upon a user trigger. + * + * @example + * ```ts + * import { DeprecationsDetails, GetDeprecationsContext, CoreSetup } from 'src/core/server'; + * + * async function getDeprecations({ esClient, savedObjectsClient }: GetDeprecationsContext): Promise { + * const deprecations: DeprecationsDetails[] = []; + * const count = await getTimelionSheetsCount(savedObjectsClient); + * + * if (count > 0) { + * // Example of a manual correctiveAction + * deprecations.push({ + * message: `You have ${count} Timelion worksheets. The Timelion app will be removed in 8.0. To continue using your Timelion worksheets, migrate them to a dashboard.`, + * documentationUrl: + * 'https://www.elastic.co/guide/en/kibana/current/create-panels-with-timelion.html', + * level: 'warning', + * correctiveActions: { + * manualSteps: [ + * 'Navigate to the Kibana Dashboard and click "Create dashboard".', + * 'Select Timelion from the "New Visualization" window.', + * 'Open a new tab, open the Timelion app, select the chart you want to copy, then copy the chart expression.', + * 'Go to Timelion, paste the chart expression in the Timelion expression field, then click Update.', + * 'In the toolbar, click Save.', + * 'On the Save visualization window, enter the visualization Title, then click Save and return.', + * ], + * }, + * }); + * } + * + * // Example of an api correctiveAction + * deprecations.push({ + * "message": "User 'test_dashboard_user' is using a deprecated role: 'kibana_user'", + * "documentationUrl": "https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-put-user.html", + * "level": "critical", + * "correctiveActions": { + * "api": { + * "path": "/internal/security/users/test_dashboard_user", + * "method": "POST", + * "body": { + * "username": "test_dashboard_user", + * "roles": [ + * "machine_learning_user", + * "enrich_user", + * "kibana_admin" + * ], + * "full_name": "Alison Goryachev", + * "email": "alisongoryachev@gmail.com", + * "metadata": {}, + * "enabled": true + * } + * }, + * "manualSteps": [ + * "Using Kibana user management, change all users using the kibana_user role to the kibana_admin role.", + * "Using Kibana role-mapping management, change all role-mappings which assing the kibana_user role to the kibana_admin role." + * ] + * }, + * }); + * + * return deprecations; + * } + * + * + * export class Plugin() { + * setup: (core: CoreSetup) => { + * core.deprecations.registerDeprecations({ getDeprecations }); + * } + * } + * ``` + * + * @public + */ +export interface DeprecationsServiceSetup { + registerDeprecations: (deprecationContext: RegisterDeprecationsConfig) => void; +} + +/** @internal */ +export interface InternalDeprecationsServiceSetup { + getRegistry: (domainId: string) => DeprecationsServiceSetup; +} + +/** @internal */ +export interface DeprecationsSetupDeps { + http: InternalHttpServiceSetup; + elasticsearch: InternalElasticsearchServiceSetup; + coreUsageData: CoreUsageDataSetup; +} + +/** @internal */ +export class DeprecationsService implements CoreService { + private readonly logger: Logger; + + constructor(private readonly coreContext: CoreContext) { + this.logger = coreContext.logger.get('deprecations-service'); + } + + public setup({ http }: DeprecationsSetupDeps): InternalDeprecationsServiceSetup { + this.logger.debug('Setting up Deprecations service'); + const deprecationsFactory = new DeprecationsFactory({ + logger: this.logger, + }); + + registerRoutes({ http, deprecationsFactory }); + this.registerConfigDeprecationsInfo(deprecationsFactory); + + return { + getRegistry: (domainId: string): DeprecationsServiceSetup => { + const registry = deprecationsFactory.getRegistry(domainId); + return { + registerDeprecations: registry.registerDeprecations, + }; + }, + }; + } + + public start() {} + public stop() {} + + private registerConfigDeprecationsInfo(deprecationsFactory: DeprecationsFactory) { + const handledDeprecatedConfigs = this.coreContext.configService.getHandledDeprecatedConfigs(); + + for (const [domainId, deprecationsContexts] of handledDeprecatedConfigs) { + const deprecationsRegistry = deprecationsFactory.getRegistry(domainId); + deprecationsRegistry.registerDeprecations({ + getDeprecations: () => { + return deprecationsContexts.map(({ message, correctiveActions, documentationUrl }) => { + return { + level: 'critical', + message, + correctiveActions: correctiveActions ?? {}, + documentationUrl, + }; + }); + }, + }); + } + } +} diff --git a/src/core/server/deprecations/index.ts b/src/core/server/deprecations/index.ts new file mode 100644 index 00000000000000..c7d1a13800694f --- /dev/null +++ b/src/core/server/deprecations/index.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 + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export type { + DeprecationsDetails, + GetDeprecationsContext, + RegisterDeprecationsConfig, + DeprecationsGetResponse, +} from './types'; + +export type { + DeprecationsServiceSetup, + InternalDeprecationsServiceSetup, +} from './deprecations_service'; + +export { DeprecationsService } from './deprecations_service'; diff --git a/src/core/server/deprecations/routes/get.ts b/src/core/server/deprecations/routes/get.ts new file mode 100644 index 00000000000000..fed3fcfbd1809d --- /dev/null +++ b/src/core/server/deprecations/routes/get.ts @@ -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 + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import { IRouter } from '../../http'; +import { GetDeprecationsContext, DeprecationsGetResponse } from '../types'; +import { DeprecationsFactory } from '../deprecations_factory'; + +interface RouteDependencies { + deprecationsFactory: DeprecationsFactory; +} + +export const registerGetRoute = (router: IRouter, { deprecationsFactory }: RouteDependencies) => { + router.get( + { + path: '/', + validate: false, + }, + async (context, req, res) => { + const dependencies: GetDeprecationsContext = { + esClient: context.core.elasticsearch.client, + savedObjectsClient: context.core.savedObjects.client, + }; + + const body: DeprecationsGetResponse = { + deprecations: await deprecationsFactory.getAllDeprecations(dependencies), + }; + + return res.ok({ body }); + } + ); +}; diff --git a/src/core/server/deprecations/routes/index.ts b/src/core/server/deprecations/routes/index.ts new file mode 100644 index 00000000000000..db58bec29f7b87 --- /dev/null +++ b/src/core/server/deprecations/routes/index.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 + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { InternalHttpServiceSetup } from '../../http'; +import { registerGetRoute } from './get'; +import { DeprecationsFactory } from '../deprecations_factory'; + +export function registerRoutes({ + http, + deprecationsFactory, +}: { + http: InternalHttpServiceSetup; + deprecationsFactory: DeprecationsFactory; +}) { + const router = http.createRouter('/api/deprecations'); + registerGetRoute(router, { deprecationsFactory }); +} diff --git a/src/core/server/deprecations/types.ts b/src/core/server/deprecations/types.ts new file mode 100644 index 00000000000000..31734b51b46bd2 --- /dev/null +++ b/src/core/server/deprecations/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 + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { SavedObjectsClientContract } from '../saved_objects/types'; +import type { IScopedClusterClient } from '../elasticsearch'; + +type MaybePromise = T | Promise; + +export interface DomainDeprecationDetails extends DeprecationsDetails { + domainId: string; +} + +export interface DeprecationsDetails { + /* The message to be displayed for the deprecation. */ + message: string; + /** + * levels: + * - warning: will not break deployment upon upgrade + * - critical: needs to be addressed before upgrade. + * - fetch_error: Deprecations service failed to grab the deprecation details for the domain. + */ + level: 'warning' | 'critical' | 'fetch_error'; + /* (optional) link to the documentation for more details on the deprecation. */ + documentationUrl?: string; + /* corrective action needed to fix this deprecation. */ + correctiveActions: { + /** + * (optional) The api to be called to automatically fix the deprecation + * Each domain should implement a POST/PUT route for their plugin to + * handle their deprecations. + */ + api?: { + /* Kibana route path. Passing a query string is allowed */ + path: string; + /* Kibana route method: 'POST' or 'PUT'. */ + method: 'POST' | 'PUT'; + /* Additional details to be passed to the route. */ + body?: { + [key: string]: any; + }; + }; + /** + * (optional) If this deprecation cannot be automtically fixed + * via an API corrective action. Specify a list of manual steps + * users need to follow to fix the deprecation before upgrade. + */ + manualSteps?: string[]; + }; +} + +export interface RegisterDeprecationsConfig { + getDeprecations: (context: GetDeprecationsContext) => MaybePromise; +} + +export interface GetDeprecationsContext { + esClient: IScopedClusterClient; + savedObjectsClient: SavedObjectsClientContract; +} + +export interface DeprecationsGetResponse { + deprecations: DomainDeprecationDetails[]; +} diff --git a/src/core/server/elasticsearch/elasticsearch_config.test.ts b/src/core/server/elasticsearch/elasticsearch_config.test.ts index 4b6cf220ccd525..23b804b5354056 100644 --- a/src/core/server/elasticsearch/elasticsearch_config.test.ts +++ b/src/core/server/elasticsearch/elasticsearch_config.test.ts @@ -28,7 +28,7 @@ const applyElasticsearchDeprecations = (settings: Record = {}) => { deprecation, path: CONFIG_PATH, })), - (msg) => deprecationMessages.push(msg) + () => ({ message }) => deprecationMessages.push(message) ); return { messages: deprecationMessages, diff --git a/src/core/server/elasticsearch/elasticsearch_config.ts b/src/core/server/elasticsearch/elasticsearch_config.ts index d3432344f5a739..e731af4817955b 100644 --- a/src/core/server/elasticsearch/elasticsearch_config.ts +++ b/src/core/server/elasticsearch/elasticsearch_config.ts @@ -144,32 +144,32 @@ export const configSchema = schema.object({ }); const deprecations: ConfigDeprecationProvider = () => [ - (settings, fromPath, log) => { + (settings, fromPath, addDeprecation) => { const es = settings[fromPath]; if (!es) { return settings; } if (es.username === 'elastic') { - log( - `Setting [${fromPath}.username] to "elastic" is deprecated. You should use the "kibana_system" user instead.` - ); + addDeprecation({ + message: `Setting [${fromPath}.username] to "elastic" is deprecated. You should use the "kibana_system" user instead.`, + }); } else if (es.username === 'kibana') { - log( - `Setting [${fromPath}.username] to "kibana" is deprecated. You should use the "kibana_system" user instead.` - ); + addDeprecation({ + message: `Setting [${fromPath}.username] to "kibana" is deprecated. You should use the "kibana_system" user instead.`, + }); } if (es.ssl?.key !== undefined && es.ssl?.certificate === undefined) { - log( - `Setting [${fromPath}.ssl.key] without [${fromPath}.ssl.certificate] is deprecated. This has no effect, you should use both settings to enable TLS client authentication to Elasticsearch.` - ); + addDeprecation({ + message: `Setting [${fromPath}.ssl.key] without [${fromPath}.ssl.certificate] is deprecated. This has no effect, you should use both settings to enable TLS client authentication to Elasticsearch.`, + }); } else if (es.ssl?.certificate !== undefined && es.ssl?.key === undefined) { - log( - `Setting [${fromPath}.ssl.certificate] without [${fromPath}.ssl.key] is deprecated. This has no effect, you should use both settings to enable TLS client authentication to Elasticsearch.` - ); + addDeprecation({ + message: `Setting [${fromPath}.ssl.certificate] without [${fromPath}.ssl.key] is deprecated. This has no effect, you should use both settings to enable TLS client authentication to Elasticsearch.`, + }); } else if (es.logQueries === true) { - log( - `Setting [${fromPath}.logQueries] is deprecated and no longer used. You should set the log level to "debug" for the "elasticsearch.queries" context in "logging.loggers" or use "logging.verbose: true".` - ); + addDeprecation({ + message: `Setting [${fromPath}.logQueries] is deprecated and no longer used. You should set the log level to "debug" for the "elasticsearch.queries" context in "logging.loggers" or use "logging.verbose: true".`, + }); } return settings; }, diff --git a/src/core/server/index.ts b/src/core/server/index.ts index 788c179501a80e..963b69eac4f7f8 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -57,7 +57,7 @@ import { StatusServiceSetup } from './status'; import { AppenderConfigType, appendersSchema, LoggingServiceSetup } from './logging'; import { CoreUsageDataStart } from './core_usage_data'; import { I18nServiceSetup } from './i18n'; - +import { DeprecationsServiceSetup } from './deprecations'; // Because of #79265 we need to explicity import, then export these types for // scripts/telemetry_check.js to work as expected import { @@ -88,8 +88,8 @@ export type { ConfigService, ConfigDeprecation, ConfigDeprecationProvider, - ConfigDeprecationLogger, ConfigDeprecationFactory, + AddConfigDeprecation, EnvironmentMode, PackageInfo, } from './config'; @@ -381,6 +381,12 @@ export type { } from './metrics'; export type { I18nServiceSetup } from './i18n'; +export type { + DeprecationsDetails, + RegisterDeprecationsConfig, + GetDeprecationsContext, + DeprecationsServiceSetup, +} from './deprecations'; export type { AppCategory } from '../types'; export { DEFAULT_APP_CATEGORIES } from '../utils'; @@ -481,6 +487,8 @@ export interface CoreSetup; } diff --git a/src/core/server/internal_types.ts b/src/core/server/internal_types.ts index 28194a7d0dc3a1..34193f8d0c35e0 100644 --- a/src/core/server/internal_types.ts +++ b/src/core/server/internal_types.ts @@ -29,6 +29,7 @@ import { InternalStatusServiceSetup } from './status'; import { InternalLoggingServiceSetup } from './logging'; import { CoreUsageDataStart } from './core_usage_data'; import { I18nServiceSetup } from './i18n'; +import { InternalDeprecationsServiceSetup } from './deprecations'; /** @internal */ export interface InternalCoreSetup { @@ -45,6 +46,7 @@ export interface InternalCoreSetup { httpResources: InternalHttpResourcesSetup; logging: InternalLoggingServiceSetup; metrics: InternalMetricsServiceSetup; + deprecations: InternalDeprecationsServiceSetup; } /** diff --git a/src/core/server/kibana_config.test.ts b/src/core/server/kibana_config.test.ts index 1c2b268156531b..1acdff9dd78e67 100644 --- a/src/core/server/kibana_config.test.ts +++ b/src/core/server/kibana_config.test.ts @@ -22,7 +22,7 @@ const applyKibanaDeprecations = (settings: Record = {}) => { deprecation, path: CONFIG_PATH, })), - (msg) => deprecationMessages.push(msg) + () => ({ message }) => deprecationMessages.push(message) ); return { messages: deprecationMessages, diff --git a/src/core/server/kibana_config.ts b/src/core/server/kibana_config.ts index d0ff18b381179f..97783a7657db51 100644 --- a/src/core/server/kibana_config.ts +++ b/src/core/server/kibana_config.ts @@ -12,12 +12,13 @@ import { ConfigDeprecationProvider } from '@kbn/config'; export type KibanaConfigType = TypeOf; const deprecations: ConfigDeprecationProvider = () => [ - (settings, fromPath, log) => { + (settings, fromPath, addDeprecation) => { const kibana = settings[fromPath]; if (kibana?.index) { - log( - `"kibana.index" is deprecated. Multitenancy by changing "kibana.index" will not be supported starting in 8.0. See https://ela.st/kbn-remove-legacy-multitenancy for more details` - ); + addDeprecation({ + message: `"kibana.index" is deprecated. Multitenancy by changing "kibana.index" will not be supported starting in 8.0. See https://ela.st/kbn-remove-legacy-multitenancy for more details`, + documentationUrl: 'https://ela.st/kbn-remove-legacy-multitenancy', + }); } return settings; }, diff --git a/src/core/server/legacy/legacy_service.test.ts b/src/core/server/legacy/legacy_service.test.ts index db36bd73602c47..d0a02b9859960b 100644 --- a/src/core/server/legacy/legacy_service.test.ts +++ b/src/core/server/legacy/legacy_service.test.ts @@ -32,6 +32,7 @@ import { statusServiceMock } from '../status/status_service.mock'; import { loggingServiceMock } from '../logging/logging_service.mock'; import { metricsServiceMock } from '../metrics/metrics_service.mock'; import { i18nServiceMock } from '../i18n/i18n_service.mock'; +import { deprecationsServiceMock } from '../deprecations/deprecations_service.mock'; const MockKbnServer: jest.Mock = KbnServer as any; @@ -80,6 +81,7 @@ beforeEach(() => { status: statusServiceMock.createInternalSetupContract(), logging: loggingServiceMock.createInternalSetupContract(), metrics: metricsServiceMock.createInternalSetupContract(), + deprecations: deprecationsServiceMock.createInternalSetupContract(), }, plugins: { 'plugin-id': 'plugin-value' }, uiPlugins: { diff --git a/src/core/server/legacy/legacy_service.ts b/src/core/server/legacy/legacy_service.ts index f7abe942d0009d..43b348a5ff4a24 100644 --- a/src/core/server/legacy/legacy_service.ts +++ b/src/core/server/legacy/legacy_service.ts @@ -257,6 +257,11 @@ export class LegacyService implements CoreService { uiSettings: { register: setupDeps.core.uiSettings.register, }, + deprecations: { + registerDeprecations: () => { + throw new Error('core.setup.deprecations.registerDeprecations is unsupported in legacy'); + }, + }, getStartServices: () => Promise.resolve([coreStart, startDeps.plugins, {}]), }; diff --git a/src/core/server/mocks.ts b/src/core/server/mocks.ts index 19056ae1b9bc76..cd0ce7005cc415 100644 --- a/src/core/server/mocks.ts +++ b/src/core/server/mocks.ts @@ -29,6 +29,7 @@ import { environmentServiceMock } from './environment/environment_service.mock'; import { statusServiceMock } from './status/status_service.mock'; import { coreUsageDataServiceMock } from './core_usage_data/core_usage_data_service.mock'; import { i18nServiceMock } from './i18n/i18n_service.mock'; +import { deprecationsServiceMock } from './deprecations/deprecations_service.mock'; export { configServiceMock } from './config/mocks'; export { httpServerMock } from './http/http_server.mocks'; @@ -49,6 +50,7 @@ export { contextServiceMock } from './context/context_service.mock'; export { capabilitiesServiceMock } from './capabilities/capabilities_service.mock'; export { coreUsageDataServiceMock } from './core_usage_data/core_usage_data_service.mock'; export { i18nServiceMock } from './i18n/i18n_service.mock'; +export { deprecationsServiceMock } from './deprecations/deprecations_service.mock'; export function pluginInitializerContextConfigMock(config: T) { const globalConfig: SharedGlobalConfig = { @@ -137,6 +139,7 @@ function createCoreSetupMock({ uiSettings: uiSettingsMock, logging: loggingServiceMock.createSetupContract(), metrics: metricsServiceMock.createSetupContract(), + deprecations: deprecationsServiceMock.createSetupContract(), getStartServices: jest .fn, object, any]>, []>() .mockResolvedValue([createCoreStartMock(), pluginStartDeps, pluginStartContract]), @@ -174,6 +177,7 @@ function createInternalCoreSetupMock() { uiSettings: uiSettingsServiceMock.createSetupContract(), logging: loggingServiceMock.createInternalSetupContract(), metrics: metricsServiceMock.createInternalSetupContract(), + deprecations: deprecationsServiceMock.createInternalSetupContract(), }; return setupDeps; } diff --git a/src/core/server/plugins/plugin_context.ts b/src/core/server/plugins/plugin_context.ts index 74451f38b893eb..c466eb2b9ee09d 100644 --- a/src/core/server/plugins/plugin_context.ts +++ b/src/core/server/plugins/plugin_context.ts @@ -165,6 +165,7 @@ export function createPluginSetupContext( register: deps.uiSettings.register, }, getStartServices: () => plugin.startDependencies, + deprecations: deps.deprecations.getRegistry(plugin.name), }; } diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 551471d3d3ba81..de96c5ccfb81ed 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -4,6 +4,7 @@ ```ts +import { AddConfigDeprecation } from '@kbn/config'; import { ApiResponse } from '@elastic/elasticsearch/lib/Transport'; import Boom from '@hapi/boom'; import { BulkIndexDocumentsParams } from 'elasticsearch'; @@ -35,7 +36,6 @@ import { ClusterStateParams } from 'elasticsearch'; import { ClusterStatsParams } from 'elasticsearch'; import { ConfigDeprecation } from '@kbn/config'; import { ConfigDeprecationFactory } from '@kbn/config'; -import { ConfigDeprecationLogger } from '@kbn/config'; import { ConfigDeprecationProvider } from '@kbn/config'; import { ConfigOptions } from 'elasticsearch'; import { ConfigPath } from '@kbn/config'; @@ -169,6 +169,8 @@ import { UpdateDocumentByQueryParams } from 'elasticsearch'; import { UpdateDocumentParams } from 'elasticsearch'; import { URL } from 'url'; +export { AddConfigDeprecation } + // @public export interface AppCategory { ariaLabel?: string; @@ -374,8 +376,6 @@ export { ConfigDeprecation } export { ConfigDeprecationFactory } -export { ConfigDeprecationLogger } - export { ConfigDeprecationProvider } export { ConfigPath } @@ -491,6 +491,8 @@ export interface CoreSetup; @@ -830,12 +832,40 @@ export interface DeprecationInfo { url: string; } +// Warning: (ae-missing-release-tag) "DeprecationsDetails" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface DeprecationsDetails { + // (undocumented) + correctiveActions: { + api?: { + path: string; + method: 'POST' | 'PUT'; + body?: { + [key: string]: any; + }; + }; + manualSteps?: string[]; + }; + // (undocumented) + documentationUrl?: string; + level: 'warning' | 'critical' | 'fetch_error'; + // (undocumented) + message: string; +} + // @public export interface DeprecationSettings { docLinksKey: string; message: string; } +// @public +export interface DeprecationsServiceSetup { + // (undocumented) + registerDeprecations: (deprecationContext: RegisterDeprecationsConfig) => void; +} + // @public export type DestructiveRouteMethod = 'post' | 'put' | 'delete' | 'patch'; @@ -939,6 +969,16 @@ export type GetAuthState = (request: KibanaRequest | LegacyRequest) state: T; }; +// Warning: (ae-missing-release-tag) "GetDeprecationsContext" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface GetDeprecationsContext { + // (undocumented) + esClient: IScopedClusterClient; + // (undocumented) + savedObjectsClient: SavedObjectsClientContract; +} + // @public (undocumented) export interface GetResponse { // (undocumented) @@ -1912,6 +1952,16 @@ export type RedirectResponseOptions = HttpResponseOptions & { }; }; +// Warning: (ae-missing-release-tag) "RegisterDeprecationsConfig" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface RegisterDeprecationsConfig { + // Warning: (ae-forgotten-export) The symbol "MaybePromise" needs to be exported by the entry point index.d.ts + // + // (undocumented) + getDeprecations: (context: GetDeprecationsContext) => MaybePromise; +} + // @public export type RequestHandler

= (context: Context, request: KibanaRequest, response: ResponseFactory) => IKibanaResponse | Promise>; diff --git a/src/core/server/server.ts b/src/core/server/server.ts index 8905bcd28fe17c..b575b2779082cf 100644 --- a/src/core/server/server.ts +++ b/src/core/server/server.ts @@ -41,6 +41,7 @@ import { ContextService } from './context'; import { RequestHandlerContext } from '.'; import { InternalCoreSetup, InternalCoreStart, ServiceConfigDescriptor } from './internal_types'; import { CoreUsageDataService } from './core_usage_data'; +import { DeprecationsService } from './deprecations'; import { CoreRouteHandlerContext } from './core_route_handler_context'; import { config as externalUrlConfig } from './external_url'; @@ -67,6 +68,7 @@ export class Server { private readonly coreApp: CoreApp; private readonly coreUsageData: CoreUsageDataService; private readonly i18n: I18nService; + private readonly deprecations: DeprecationsService; private readonly savedObjectsStartPromise: Promise; private resolveSavedObjectsStartPromise?: (value: SavedObjectsServiceStart) => void; @@ -102,6 +104,7 @@ export class Server { this.logging = new LoggingService(core); this.coreUsageData = new CoreUsageDataService(core); this.i18n = new I18nService(core); + this.deprecations = new DeprecationsService(core); this.savedObjectsStartPromise = new Promise((resolve) => { this.resolveSavedObjectsStartPromise = resolve; @@ -192,6 +195,12 @@ export class Server { loggingSystem: this.loggingSystem, }); + const deprecationsSetup = this.deprecations.setup({ + http: httpSetup, + elasticsearch: elasticsearchServiceSetup, + coreUsageData: coreUsageDataSetup, + }); + const coreSetup: InternalCoreSetup = { capabilities: capabilitiesSetup, context: contextServiceSetup, @@ -206,6 +215,7 @@ export class Server { httpResources: httpResourcesSetup, logging: loggingSetup, metrics: metricsSetup, + deprecations: deprecationsSetup, }; const pluginsSetup = await this.plugins.setup(coreSetup); @@ -285,6 +295,7 @@ export class Server { await this.metrics.stop(); await this.status.stop(); await this.logging.stop(); + this.deprecations.stop(); } private registerCoreContext(coreSetup: InternalCoreSetup) { diff --git a/src/core/server/types.ts b/src/core/server/types.ts index 6bd805d55af1d3..ab1d6c6d95d0a9 100644 --- a/src/core/server/types.ts +++ b/src/core/server/types.ts @@ -37,6 +37,7 @@ export type { SavedObjectsClientContract, SavedObjectsNamespaceType, } from './saved_objects/types'; +export type { DomainDeprecationDetails, DeprecationsGetResponse } from './deprecations/types'; export * from './ui_settings/types'; export * from './legacy/types'; export type { EnvironmentMode, PackageInfo } from '@kbn/config'; diff --git a/src/plugins/kibana_legacy/server/index.ts b/src/plugins/kibana_legacy/server/index.ts index 15511e1e9f7f20..1402416d69c967 100644 --- a/src/plugins/kibana_legacy/server/index.ts +++ b/src/plugins/kibana_legacy/server/index.ts @@ -6,12 +6,7 @@ * Side Public License, v 1. */ -import { - ConfigDeprecationLogger, - CoreSetup, - CoreStart, - PluginConfigDescriptor, -} from 'kibana/server'; +import { AddConfigDeprecation, CoreSetup, CoreStart, PluginConfigDescriptor } from 'kibana/server'; import { get } from 'lodash'; import { configSchema, ConfigSchema } from '../config'; @@ -23,17 +18,28 @@ export const config: PluginConfigDescriptor = { schema: configSchema, deprecations: ({ renameFromRoot }) => [ // TODO: Remove deprecation once defaultAppId is deleted - renameFromRoot('kibana.defaultAppId', 'kibana_legacy.defaultAppId', true), - (completeConfig: Record, rootPath: string, log: ConfigDeprecationLogger) => { + renameFromRoot('kibana.defaultAppId', 'kibana_legacy.defaultAppId', { silent: true }), + ( + completeConfig: Record, + rootPath: string, + addDeprecation: AddConfigDeprecation + ) => { if ( get(completeConfig, 'kibana.defaultAppId') === undefined && get(completeConfig, 'kibana_legacy.defaultAppId') === undefined ) { return completeConfig; } - log( - `kibana.defaultAppId is deprecated and will be removed in 8.0. Please use the \`defaultRoute\` advanced setting instead` - ); + addDeprecation({ + message: `kibana.defaultAppId is deprecated and will be removed in 8.0. Please use the \`defaultRoute\` advanced setting instead`, + correctiveActions: { + manualSteps: [ + 'Go to Stack Management > Advanced Settings', + 'Update the "defaultRoute" setting under the General section', + 'Remove "kibana.defaultAppId" from the kibana.yml config file', + ], + }, + }); return completeConfig; }, ], diff --git a/src/plugins/timelion/server/deprecations.ts b/src/plugins/timelion/server/deprecations.ts new file mode 100644 index 00000000000000..3d4e687f154cfb --- /dev/null +++ b/src/plugins/timelion/server/deprecations.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 + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { + CoreStart, + SavedObjectsClient, + Logger, + GetDeprecationsContext, + DeprecationsDetails, +} from 'src/core/server'; + +export const getTimelionSheetsCount = async ( + savedObjectsClient: Pick +) => { + const { total } = await savedObjectsClient.find({ type: 'timelion-sheet', perPage: 1 }); + return total; +}; + +export const showWarningMessageIfTimelionSheetWasFound = async ( + core: CoreStart, + logger: Logger +) => { + const { savedObjects } = core; + const savedObjectsClient = savedObjects.createInternalRepository(); + const count = await getTimelionSheetsCount(savedObjectsClient); + if (count > 0) { + logger.warn( + 'Deprecated since 7.0, the Timelion app will be removed in 8.0. To continue using your Timelion worksheets, migrate them to a dashboard. See https://www.elastic.co/guide/en/kibana/current/create-panels-with-timelion.html.' + ); + } +}; + +/** + * Deprecated since 7.0, the Timelion app will be removed in 8.0. + * To continue using your Timelion worksheets, migrate them to a dashboard. + * + * @link https://www.elastic.co/guide/en/kibana/master/timelion.html#timelion-deprecation + **/ +export async function getDeprecations({ + savedObjectsClient, +}: GetDeprecationsContext): Promise { + const deprecations: DeprecationsDetails[] = []; + const count = await getTimelionSheetsCount(savedObjectsClient); + + if (count > 0) { + deprecations.push({ + message: `You have ${count} Timelion worksheets. The Timelion app will be removed in 8.0. To continue using your Timelion worksheets, migrate them to a dashboard.`, + documentationUrl: + 'https://www.elastic.co/guide/en/kibana/current/create-panels-with-timelion.html', + level: 'warning', + correctiveActions: { + manualSteps: [ + 'Navigate to the Kibana Dashboard and click "Create dashboard".', + 'Select Timelion from the "New Visualization" window.', + 'Open a new tab, open the Timelion app, select the chart you want to copy, then copy the chart expression.', + 'Go to Timelion, paste the chart expression in the Timelion expression field, then click Update.', + 'In the toolbar, click Save.', + 'On the Save visualization window, enter the visualization Title, then click Save and return.', + ], + }, + }); + } + + return deprecations; +} diff --git a/src/plugins/timelion/server/plugin.ts b/src/plugins/timelion/server/plugin.ts index 226a978fe5d88c..edbba9b565ae4c 100644 --- a/src/plugins/timelion/server/plugin.ts +++ b/src/plugins/timelion/server/plugin.ts @@ -11,30 +11,7 @@ import { i18n } from '@kbn/i18n'; import { schema } from '@kbn/config-schema'; import { TimelionConfigType } from './config'; import { timelionSheetSavedObjectType } from './saved_objects'; - -/** - * Deprecated since 7.0, the Timelion app will be removed in 8.0. - * To continue using your Timelion worksheets, migrate them to a dashboard. - * - * @link https://www.elastic.co/guide/en/kibana/master/timelion.html#timelion-deprecation - **/ -const showWarningMessageIfTimelionSheetWasFound = (core: CoreStart, logger: Logger) => { - const { savedObjects } = core; - const savedObjectsClient = savedObjects.createInternalRepository(); - - savedObjectsClient - .find({ - type: 'timelion-sheet', - perPage: 1, - }) - .then( - ({ total }) => - total && - logger.warn( - 'Deprecated since 7.0, the Timelion app will be removed in 8.0. To continue using your Timelion worksheets, migrate them to a dashboard. See https://www.elastic.co/guide/en/kibana/current/create-panels-with-timelion.html.' - ) - ); -}; +import { getDeprecations, showWarningMessageIfTimelionSheetWasFound } from './deprecations'; export class TimelionPlugin implements Plugin { private logger: Logger; @@ -87,6 +64,8 @@ export class TimelionPlugin implements Plugin { schema: schema.number(), }, }); + + core.deprecations.registerDeprecations({ getDeprecations }); } start(core: CoreStart) { showWarningMessageIfTimelionSheetWasFound(core, this.logger); diff --git a/src/plugins/vis_type_timelion/server/index.ts b/src/plugins/vis_type_timelion/server/index.ts index 1dcb7263c48182..35f4182a50a861 100644 --- a/src/plugins/vis_type_timelion/server/index.ts +++ b/src/plugins/vis_type_timelion/server/index.ts @@ -21,7 +21,7 @@ export const config: PluginConfigDescriptor = { renameFromRoot('timelion_vis.enabled', 'vis_type_timelion.enabled'), renameFromRoot('timelion.enabled', 'vis_type_timelion.enabled'), renameFromRoot('timelion.graphiteUrls', 'vis_type_timelion.graphiteUrls'), - renameFromRoot('timelion.ui.enabled', 'vis_type_timelion.ui.enabled', true), + renameFromRoot('timelion.ui.enabled', 'vis_type_timelion.ui.enabled', { silent: true }), ], }; export const plugin = (initializerContext: PluginInitializerContext) => diff --git a/src/plugins/vis_type_timeseries/server/index.ts b/src/plugins/vis_type_timeseries/server/index.ts index 37eda1b1338d4f..3c27713c37500c 100644 --- a/src/plugins/vis_type_timeseries/server/index.ts +++ b/src/plugins/vis_type_timeseries/server/index.ts @@ -15,9 +15,13 @@ export { VisTypeTimeseriesSetup } from './plugin'; export const config: PluginConfigDescriptor = { deprecations: ({ unused, renameFromRoot }) => [ // In Kibana v7.8 plugin id was renamed from 'metrics' to 'vis_type_timeseries': - renameFromRoot('metrics.enabled', 'vis_type_timeseries.enabled', true), - renameFromRoot('metrics.chartResolution', 'vis_type_timeseries.chartResolution', true), - renameFromRoot('metrics.minimumBucketSize', 'vis_type_timeseries.minimumBucketSize', true), + renameFromRoot('metrics.enabled', 'vis_type_timeseries.enabled', { silent: true }), + renameFromRoot('metrics.chartResolution', 'vis_type_timeseries.chartResolution', { + silent: true, + }), + renameFromRoot('metrics.minimumBucketSize', 'vis_type_timeseries.minimumBucketSize', { + silent: true, + }), // Unused properties which should be removed after releasing Kibana v8.0: unused('chartResolution'), diff --git a/test/functional/fixtures/es_archiver/deprecations_service/data.json b/test/functional/fixtures/es_archiver/deprecations_service/data.json new file mode 100644 index 00000000000000..31ce5af20b46c2 --- /dev/null +++ b/test/functional/fixtures/es_archiver/deprecations_service/data.json @@ -0,0 +1,14 @@ +{ + "type": "doc", + "value": { + "index": ".kibana", + "id": "test-deprecations-plugin:ff3733a0-9fty-11e7-ahb3-3dcb94193fab", + "source": { + "type": "test-deprecations-plugin", + "updated_at": "2021-02-11T18:51:23.794Z", + "test-deprecations-plugin": { + "title": "Test saved object" + } + } + } +} diff --git a/test/functional/fixtures/es_archiver/deprecations_service/mappings.json b/test/functional/fixtures/es_archiver/deprecations_service/mappings.json new file mode 100644 index 00000000000000..5f7c7e0e7b7dca --- /dev/null +++ b/test/functional/fixtures/es_archiver/deprecations_service/mappings.json @@ -0,0 +1,289 @@ +{ + "type": "index", + "value": { + "index": ".kibana", + "mappings": { + "properties": { + "config": { + "dynamic": "true", + "properties": { + "buildNum": { + "type": "keyword" + }, + "dateFormat:tz": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "dashboard": { + "dynamic": "strict", + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "optionsJSON": { + "type": "text" + }, + "panelsJSON": { + "type": "text" + }, + "refreshInterval": { + "properties": { + "display": { + "type": "keyword" + }, + "pause": { + "type": "boolean" + }, + "section": { + "type": "integer" + }, + "value": { + "type": "integer" + } + } + }, + "timeFrom": { + "type": "keyword" + }, + "timeRestore": { + "type": "boolean" + }, + "timeTo": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "index-pattern": { + "dynamic": "strict", + "properties": { + "fieldFormatMap": { + "type": "text" + }, + "fields": { + "type": "text" + }, + "intervalName": { + "type": "keyword" + }, + "notExpandable": { + "type": "boolean" + }, + "sourceFilters": { + "type": "text" + }, + "timeFieldName": { + "type": "keyword" + }, + "title": { + "type": "text" + } + } + }, + "search": { + "dynamic": "strict", + "properties": { + "columns": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "sort": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "server": { + "dynamic": "strict", + "properties": { + "uuid": { + "type": "keyword" + } + } + }, + "timelion-sheet": { + "dynamic": "strict", + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "timelion_chart_height": { + "type": "integer" + }, + "timelion_columns": { + "type": "integer" + }, + "timelion_interval": { + "type": "keyword" + }, + "timelion_other_interval": { + "type": "keyword" + }, + "timelion_rows": { + "type": "integer" + }, + "timelion_sheet": { + "type": "text" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "type": { + "type": "keyword" + }, + "url": { + "dynamic": "strict", + "properties": { + "accessCount": { + "type": "long" + }, + "accessDate": { + "type": "date" + }, + "createDate": { + "type": "date" + }, + "url": { + "fields": { + "keyword": { + "ignore_above": 2048, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "visualization": { + "dynamic": "strict", + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "savedSearchId": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "visState": { + "type": "text" + } + } + }, + "query": { + "properties": { + "title": { + "type": "text" + }, + "description": { + "type": "text" + }, + "query": { + "properties": { + "language": { + "type": "keyword" + }, + "query": { + "type": "keyword", + "index": false + } + } + }, + "filters": { + "type": "object", + "enabled": false + }, + "timefilter": { + "type": "object", + "enabled": false + } + } + }, + "test-deprecations-plugin": { + "properties": { + "title": { + "type": "text" + } + } + } + } + }, + "settings": { + "index": { + "number_of_replicas": "0", + "number_of_shards": "1" + } + } + } +} diff --git a/test/plugin_functional/config.ts b/test/plugin_functional/config.ts index fc747fcd71f17b..1651e213ee82d6 100644 --- a/test/plugin_functional/config.ts +++ b/test/plugin_functional/config.ts @@ -56,6 +56,9 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { // Required to load new platform plugins via `--plugin-path` flag. '--env.name=development', + '--corePluginDeprecations.oldProperty=hello', + '--corePluginDeprecations.secret=100', + '--corePluginDeprecations.noLongerUsed=still_using', ...plugins.map( (pluginDir) => `--plugin-path=${path.resolve(__dirname, 'plugins', pluginDir)}` ), diff --git a/test/plugin_functional/plugins/core_plugin_deprecations/kibana.json b/test/plugin_functional/plugins/core_plugin_deprecations/kibana.json new file mode 100644 index 00000000000000..bc251f97bea587 --- /dev/null +++ b/test/plugin_functional/plugins/core_plugin_deprecations/kibana.json @@ -0,0 +1,8 @@ +{ + "id": "corePluginDeprecations", + "version": "0.0.1", + "kibanaVersion": "kibana", + "configPath": ["corePluginDeprecations"], + "server": true, + "ui": false +} diff --git a/test/plugin_functional/plugins/core_plugin_deprecations/package.json b/test/plugin_functional/plugins/core_plugin_deprecations/package.json new file mode 100644 index 00000000000000..f14ec933f59b2b --- /dev/null +++ b/test/plugin_functional/plugins/core_plugin_deprecations/package.json @@ -0,0 +1,14 @@ +{ + "name": "core_plugin_deprecations", + "version": "1.0.0", + "main": "target/test/plugin_functional/plugins/core_plugin_deprecations", + "kibana": { + "version": "kibana", + "templateVersion": "1.0.0" + }, + "license": "SSPL-1.0 OR Elastic License 2.0", + "scripts": { + "kbn": "node ../../../../scripts/kbn.js", + "build": "rm -rf './target' && ../../../../node_modules/.bin/tsc" + } +} diff --git a/test/plugin_functional/plugins/core_plugin_deprecations/public/application.tsx b/test/plugin_functional/plugins/core_plugin_deprecations/public/application.tsx new file mode 100644 index 00000000000000..e2166a249e34b2 --- /dev/null +++ b/test/plugin_functional/plugins/core_plugin_deprecations/public/application.tsx @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import { AppMountParameters } from 'kibana/public'; + +const DeprecationsApp = () =>

Deprcations App
; + +export const renderApp = ({ element }: AppMountParameters) => { + ReactDOM.render(, element); + + return () => ReactDOM.unmountComponentAtNode(element); +}; diff --git a/test/plugin_functional/plugins/core_plugin_deprecations/public/index.ts b/test/plugin_functional/plugins/core_plugin_deprecations/public/index.ts new file mode 100644 index 00000000000000..bb6b3f0740b3bd --- /dev/null +++ b/test/plugin_functional/plugins/core_plugin_deprecations/public/index.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { PluginInitializer, PluginInitializerContext } from 'kibana/public'; +import { + CorePluginDeprecationsPlugin, + CorePluginDeprecationsPluginSetup, + CorePluginDeprecationsPluginStart, +} from './plugin'; + +export const plugin: PluginInitializer< + CorePluginDeprecationsPluginSetup, + CorePluginDeprecationsPluginStart +> = (context: PluginInitializerContext) => new CorePluginDeprecationsPlugin(context); diff --git a/test/plugin_functional/plugins/core_plugin_deprecations/public/plugin.tsx b/test/plugin_functional/plugins/core_plugin_deprecations/public/plugin.tsx new file mode 100644 index 00000000000000..bf807145e14bfe --- /dev/null +++ b/test/plugin_functional/plugins/core_plugin_deprecations/public/plugin.tsx @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { CoreSetup, Plugin, PluginInitializerContext } from 'kibana/public'; + +declare global { + interface Window { + env?: PluginInitializerContext['env']; + } +} + +export class CorePluginDeprecationsPlugin + implements Plugin { + constructor(pluginContext: PluginInitializerContext) { + window.env = pluginContext.env; + } + public setup(core: CoreSetup) { + core.application.register({ + id: 'core-plugin-deprecations', + title: 'Core Plugin Deprecations', + async mount(params) { + const { renderApp } = await import('./application'); + await core.getStartServices(); + return renderApp(params); + }, + }); + } + + public start() {} + + public stop() {} +} + +export type CorePluginDeprecationsPluginSetup = ReturnType; +export type CorePluginDeprecationsPluginStart = ReturnType; diff --git a/test/plugin_functional/plugins/core_plugin_deprecations/server/config.ts b/test/plugin_functional/plugins/core_plugin_deprecations/server/config.ts new file mode 100644 index 00000000000000..db4288d26a3d7b --- /dev/null +++ b/test/plugin_functional/plugins/core_plugin_deprecations/server/config.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 + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { schema, TypeOf } from '@kbn/config-schema'; +import { get } from 'lodash'; +import type { PluginConfigDescriptor } from 'kibana/server'; +import type { ConfigDeprecation } from '@kbn/config'; + +const configSchema = schema.object({ + newProperty: schema.maybe(schema.string({ defaultValue: 'Some string' })), + noLongerUsed: schema.maybe(schema.string()), + secret: schema.maybe(schema.number({ defaultValue: 42 })), +}); + +type ConfigType = TypeOf; + +const configSecretDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => { + if (get(settings, 'corePluginDeprecations.secret') !== 42) { + addDeprecation({ + documentationUrl: 'config-secret-doc-url', + message: + 'Kibana plugin funcitonal tests will no longer allow corePluginDeprecations.secret ' + + 'config to be set to anything except 42.', + }); + } + return settings; +}; + +export const config: PluginConfigDescriptor = { + schema: configSchema, + deprecations: ({ rename, unused }) => [ + rename('oldProperty', 'newProperty'), + unused('noLongerUsed'), + configSecretDeprecation, + ], +}; diff --git a/test/plugin_functional/plugins/core_plugin_deprecations/server/index.ts b/test/plugin_functional/plugins/core_plugin_deprecations/server/index.ts new file mode 100644 index 00000000000000..1968c011a327aa --- /dev/null +++ b/test/plugin_functional/plugins/core_plugin_deprecations/server/index.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { CorePluginDeprecationsPlugin } from './plugin'; + +export { config } from './config'; +export const plugin = () => new CorePluginDeprecationsPlugin(); diff --git a/test/plugin_functional/plugins/core_plugin_deprecations/server/plugin.ts b/test/plugin_functional/plugins/core_plugin_deprecations/server/plugin.ts new file mode 100644 index 00000000000000..38565b1e2c0a85 --- /dev/null +++ b/test/plugin_functional/plugins/core_plugin_deprecations/server/plugin.ts @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { Plugin, CoreSetup, GetDeprecationsContext, DeprecationsDetails } from 'kibana/server'; +import { registerRoutes } from './routes'; +async function getDeprecations({ + savedObjectsClient, +}: GetDeprecationsContext): Promise { + const deprecations: DeprecationsDetails[] = []; + const { total } = await savedObjectsClient.find({ type: 'test-deprecations-plugin', perPage: 1 }); + + deprecations.push({ + message: `CorePluginDeprecationsPlugin is a deprecated feature for testing.`, + documentationUrl: 'test-url', + level: 'warning', + correctiveActions: { + manualSteps: ['Step a', 'Step b'], + }, + }); + + if (total > 0) { + deprecations.push({ + message: `SavedObject test-deprecations-plugin is still being used.`, + documentationUrl: 'another-test-url', + level: 'critical', + correctiveActions: {}, + }); + } + + return deprecations; +} + +export class CorePluginDeprecationsPlugin implements Plugin { + public setup(core: CoreSetup, deps: {}) { + registerRoutes(core.http); + core.savedObjects.registerType({ + name: 'test-deprecations-plugin', + hidden: false, + namespaceType: 'single', + mappings: { + properties: { + title: { type: 'text' }, + }, + }, + }); + + core.deprecations.registerDeprecations({ getDeprecations }); + } + + public start() {} + public stop() {} +} diff --git a/test/plugin_functional/plugins/core_plugin_deprecations/server/routes.ts b/test/plugin_functional/plugins/core_plugin_deprecations/server/routes.ts new file mode 100644 index 00000000000000..d6bf065898f938 --- /dev/null +++ b/test/plugin_functional/plugins/core_plugin_deprecations/server/routes.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { HttpServiceSetup } from 'kibana/server'; +import { schema } from '@kbn/config-schema'; + +export function registerRoutes(http: HttpServiceSetup) { + const router = http.createRouter(); + router.post( + { + path: '/api/core_deprecations_resolve/', + validate: { + body: schema.object({ + mockFail: schema.maybe(schema.boolean()), + keyId: schema.maybe(schema.string()), + deprecationDetails: schema.object({ + domainId: schema.string(), + }), + }), + }, + }, + async (context, req, res) => { + const { mockFail, keyId } = req.body; + if (mockFail === true) { + return res.badRequest({ + body: new Error('Mocking api failure'), + }); + } + + if (keyId) { + const client = context.core.savedObjects.getClient(); + await client.delete('test-deprecations-plugin', keyId, { + refresh: true, + }); + } + + return res.ok(); + } + ); +} diff --git a/test/plugin_functional/plugins/core_plugin_deprecations/tsconfig.json b/test/plugin_functional/plugins/core_plugin_deprecations/tsconfig.json new file mode 100644 index 00000000000000..3d9d8ca9451d41 --- /dev/null +++ b/test/plugin_functional/plugins/core_plugin_deprecations/tsconfig.json @@ -0,0 +1,18 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./target", + "skipLibCheck": true + }, + "include": [ + "index.ts", + "public/**/*.ts", + "public/**/*.tsx", + "server/**/*.ts", + "../../../../typings/**/*", + ], + "exclude": [], + "references": [ + { "path": "../../../../src/core/tsconfig.json" } + ] +} diff --git a/test/plugin_functional/test_suites/core/deprecations.ts b/test/plugin_functional/test_suites/core/deprecations.ts new file mode 100644 index 00000000000000..c44781ab284c66 --- /dev/null +++ b/test/plugin_functional/test_suites/core/deprecations.ts @@ -0,0 +1,247 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import expect from '@kbn/expect'; +import type { DomainDeprecationDetails, DeprecationsGetResponse } from 'src/core/server/types'; +import type { ResolveDeprecationResponse } from 'src/core/public'; +import { PluginFunctionalProviderContext } from '../../services'; + +export default function ({ getService, getPageObjects }: PluginFunctionalProviderContext) { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + const PageObjects = getPageObjects(['common']); + const browser = getService('browser'); + + const CorePluginDeprecationsPluginDeprecations = [ + { + level: 'critical', + message: + '"corePluginDeprecations.oldProperty" is deprecated and has been replaced by "corePluginDeprecations.newProperty"', + correctiveActions: { + manualSteps: [ + 'Replace "corePluginDeprecations.oldProperty" with "corePluginDeprecations.newProperty" in the Kibana config file, CLI flag, or environment variable (in Docker only).', + ], + }, + domainId: 'corePluginDeprecations', + }, + { + level: 'critical', + message: 'corePluginDeprecations.noLongerUsed is deprecated and is no longer used', + correctiveActions: { + manualSteps: [ + 'Remove "corePluginDeprecations.noLongerUsed" from the Kibana config file, CLI flag, or environment variable (in Docker only)', + ], + }, + domainId: 'corePluginDeprecations', + }, + { + level: 'critical', + message: + 'Kibana plugin funcitonal tests will no longer allow corePluginDeprecations.secret config to be set to anything except 42.', + correctiveActions: {}, + documentationUrl: 'config-secret-doc-url', + domainId: 'corePluginDeprecations', + }, + { + message: 'CorePluginDeprecationsPlugin is a deprecated feature for testing.', + documentationUrl: 'test-url', + level: 'warning', + correctiveActions: { + manualSteps: ['Step a', 'Step b'], + }, + domainId: 'corePluginDeprecations', + }, + { + message: 'SavedObject test-deprecations-plugin is still being used.', + documentationUrl: 'another-test-url', + level: 'critical', + correctiveActions: {}, + domainId: 'corePluginDeprecations', + }, + ]; + + describe('deprecations service', () => { + before(() => esArchiver.load('../functional/fixtures/es_archiver/deprecations_service')); + after(() => esArchiver.unload('../functional/fixtures/es_archiver/deprecations_service')); + + describe('GET /api/deprecations/', async () => { + it('returns registered config deprecations and feature deprecations', async () => { + const { body } = await supertest.get('/api/deprecations/').set('kbn-xsrf', 'true'); + + const { deprecations } = body as DeprecationsGetResponse; + expect(Array.isArray(deprecations)).to.be(true); + const corePluginDeprecations = deprecations.filter( + ({ domainId }) => domainId === 'corePluginDeprecations' + ); + + expect(corePluginDeprecations).to.eql(CorePluginDeprecationsPluginDeprecations); + }); + }); + + describe('Public API', () => { + before(async () => await PageObjects.common.navigateToApp('home')); + + it('#getAllDeprecations returns all deprecations plugin deprecations', async () => { + const result = await browser.executeAsync((cb) => { + return window._coreProvider.start.core.deprecations.getAllDeprecations().then(cb); + }); + + const corePluginDeprecations = result.filter( + ({ domainId }) => domainId === 'corePluginDeprecations' + ); + + expect(corePluginDeprecations).to.eql(CorePluginDeprecationsPluginDeprecations); + }); + + it('#getDeprecations returns domain deprecations', async () => { + const corePluginDeprecations = await browser.executeAsync( + (cb) => { + return window._coreProvider.start.core.deprecations + .getDeprecations('corePluginDeprecations') + .then(cb); + } + ); + + expect(corePluginDeprecations).to.eql(CorePluginDeprecationsPluginDeprecations); + }); + + describe('resolveDeprecation', () => { + it('fails on missing correctiveActions.api', async () => { + const resolveResult = await browser.executeAsync((cb) => { + return window._coreProvider.start.core.deprecations + .resolveDeprecation({ + message: 'CorePluginDeprecationsPlugin is a deprecated feature for testing.', + documentationUrl: 'test-url', + level: 'warning', + correctiveActions: { + manualSteps: ['Step a', 'Step b'], + }, + domainId: 'corePluginDeprecations', + }) + .then(cb); + }); + + expect(resolveResult).to.eql({ + reason: 'deprecation has no correctiveAction via api.', + status: 'fail', + }); + }); + + it('fails on bad request from correctiveActions.api', async () => { + const resolveResult = await browser.executeAsync((cb) => { + return window._coreProvider.start.core.deprecations + .resolveDeprecation({ + message: 'CorePluginDeprecationsPlugin is a deprecated feature for testing.', + documentationUrl: 'test-url', + level: 'warning', + correctiveActions: { + api: { + method: 'POST', + path: '/api/core_deprecations_resolve/', + body: { + mockFail: true, + }, + }, + }, + domainId: 'corePluginDeprecations', + }) + .then(cb); + }); + + expect(resolveResult).to.eql({ + reason: 'Mocking api failure', + status: 'fail', + }); + }); + + it('fails on 404 request from correctiveActions.api', async () => { + const resolveResult = await browser.executeAsync((cb) => { + return window._coreProvider.start.core.deprecations + .resolveDeprecation({ + message: 'CorePluginDeprecationsPlugin is a deprecated feature for testing.', + documentationUrl: 'test-url', + level: 'warning', + correctiveActions: { + api: { + method: 'POST', + path: '/api/invalid_route_not_registered/', + body: { + mockFail: true, + }, + }, + }, + domainId: 'corePluginDeprecations', + }) + .then(cb); + }); + + expect(resolveResult).to.eql({ + reason: 'Not Found', + status: 'fail', + }); + }); + + it('returns { status: ok } on successful correctiveActions.api', async () => { + const savedObjectId = await supertest + .get('/api/saved_objects/_find?type=test-deprecations-plugin') + .set('kbn-xsrf', 'true') + .expect(200) + .then(({ body }) => { + expect(body.total).to.be(1); + return body.saved_objects[0].id; + }); + + const resolveResult = await browser.executeAsync( + (keyId, cb) => { + return window._coreProvider.start.core.deprecations + .resolveDeprecation({ + message: 'CorePluginDeprecationsPlugin is a deprecated feature for testing.', + documentationUrl: 'test-url', + level: 'warning', + correctiveActions: { + api: { + method: 'POST', + path: '/api/core_deprecations_resolve/', + body: { keyId }, + }, + }, + domainId: 'corePluginDeprecations', + }) + .then(cb); + }, + savedObjectId + ); + + expect(resolveResult).to.eql({ status: 'ok' }); + await supertest + .get('/api/saved_objects/_find?type=test-deprecations-plugin') + .set('kbn-xsrf', 'true') + .expect(200) + .then(({ body }) => { + expect(body.total).to.be(0); + }); + + const { deprecations } = await supertest + .get('/api/deprecations/') + .set('kbn-xsrf', 'true') + .then( + ({ body }): Promise => { + return body; + } + ); + + const deprecation = deprecations.find( + ({ message }) => message === 'SavedObject test-deprecations-plugin is still being used.' + ); + + expect(deprecation).to.eql(undefined); + }); + }); + }); + }); +} diff --git a/test/plugin_functional/test_suites/core/index.ts b/test/plugin_functional/test_suites/core/index.ts index 9baa1ab0b394d1..8591c2fdec8ddb 100644 --- a/test/plugin_functional/test_suites/core/index.ts +++ b/test/plugin_functional/test_suites/core/index.ts @@ -10,6 +10,7 @@ import { PluginFunctionalProviderContext } from '../../services'; export default function ({ loadTestFile }: PluginFunctionalProviderContext) { describe('core', function () { + loadTestFile(require.resolve('./deprecations')); loadTestFile(require.resolve('./route')); }); } diff --git a/x-pack/plugins/monitoring/server/deprecations.test.js b/x-pack/plugins/monitoring/server/deprecations.test.js index d7e1a2340d2952..2931f704a44789 100644 --- a/x-pack/plugins/monitoring/server/deprecations.test.js +++ b/x-pack/plugins/monitoring/server/deprecations.test.js @@ -16,8 +16,8 @@ describe('monitoring plugin deprecations', function () { beforeAll(function () { const deprecations = deprecationsModule({ rename, renameFromRoot }); - transformDeprecations = (settings, fromPath, log = noop) => { - deprecations.forEach((deprecation) => deprecation(settings, fromPath, log)); + transformDeprecations = (settings, fromPath, addDeprecation = noop) => { + deprecations.forEach((deprecation) => deprecation(settings, fromPath, addDeprecation)); }; }); @@ -31,9 +31,9 @@ describe('monitoring plugin deprecations', function () { }, }; - const log = jest.fn(); - transformDeprecations(settings, fromPath, log); - expect(log).not.toHaveBeenCalled(); + const addDeprecation = jest.fn(); + transformDeprecations(settings, fromPath, addDeprecation); + expect(addDeprecation).not.toHaveBeenCalled(); }); it(`shouldn't log when email_address is specified`, function () { @@ -46,9 +46,9 @@ describe('monitoring plugin deprecations', function () { }, }; - const log = jest.fn(); - transformDeprecations(settings, fromPath, log); - expect(log).not.toHaveBeenCalled(); + const addDeprecation = jest.fn(); + transformDeprecations(settings, fromPath, addDeprecation); + expect(addDeprecation).not.toHaveBeenCalled(); }); it(`should log when email_address is missing, but alerts/notifications are both enabled`, function () { @@ -60,9 +60,9 @@ describe('monitoring plugin deprecations', function () { }, }; - const log = jest.fn(); - transformDeprecations(settings, fromPath, log); - expect(log).toHaveBeenCalled(); + const addDeprecation = jest.fn(); + transformDeprecations(settings, fromPath, addDeprecation); + expect(addDeprecation).toHaveBeenCalled(); }); }); @@ -70,65 +70,65 @@ describe('monitoring plugin deprecations', function () { it('logs a warning if elasticsearch.username is set to "elastic"', () => { const settings = { elasticsearch: { username: 'elastic' } }; - const log = jest.fn(); - transformDeprecations(settings, fromPath, log); - expect(log).toHaveBeenCalled(); + const addDeprecation = jest.fn(); + transformDeprecations(settings, fromPath, addDeprecation); + expect(addDeprecation).toHaveBeenCalled(); }); it('logs a warning if elasticsearch.username is set to "kibana"', () => { const settings = { elasticsearch: { username: 'kibana' } }; - const log = jest.fn(); - transformDeprecations(settings, fromPath, log); - expect(log).toHaveBeenCalled(); + const addDeprecation = jest.fn(); + transformDeprecations(settings, fromPath, addDeprecation); + expect(addDeprecation).toHaveBeenCalled(); }); it('does not log a warning if elasticsearch.username is set to something besides "elastic" or "kibana"', () => { const settings = { elasticsearch: { username: 'otheruser' } }; - const log = jest.fn(); - transformDeprecations(settings, fromPath, log); - expect(log).not.toHaveBeenCalled(); + const addDeprecation = jest.fn(); + transformDeprecations(settings, fromPath, addDeprecation); + expect(addDeprecation).not.toHaveBeenCalled(); }); it('does not log a warning if elasticsearch.username is unset', () => { const settings = { elasticsearch: { username: undefined } }; - const log = jest.fn(); - transformDeprecations(settings, fromPath, log); - expect(log).not.toHaveBeenCalled(); + const addDeprecation = jest.fn(); + transformDeprecations(settings, fromPath, addDeprecation); + expect(addDeprecation).not.toHaveBeenCalled(); }); it('logs a warning if ssl.key is set and ssl.certificate is not', () => { const settings = { elasticsearch: { ssl: { key: '' } } }; - const log = jest.fn(); - transformDeprecations(settings, fromPath, log); - expect(log).toHaveBeenCalled(); + const addDeprecation = jest.fn(); + transformDeprecations(settings, fromPath, addDeprecation); + expect(addDeprecation).toHaveBeenCalled(); }); it('logs a warning if ssl.certificate is set and ssl.key is not', () => { const settings = { elasticsearch: { ssl: { certificate: '' } } }; - const log = jest.fn(); - transformDeprecations(settings, fromPath, log); - expect(log).toHaveBeenCalled(); + const addDeprecation = jest.fn(); + transformDeprecations(settings, fromPath, addDeprecation); + expect(addDeprecation).toHaveBeenCalled(); }); it('does not log a warning if both ssl.key and ssl.certificate are set', () => { const settings = { elasticsearch: { ssl: { key: '', certificate: '' } } }; - const log = jest.fn(); - transformDeprecations(settings, fromPath, log); - expect(log).not.toHaveBeenCalled(); + const addDeprecation = jest.fn(); + transformDeprecations(settings, fromPath, addDeprecation); + expect(addDeprecation).not.toHaveBeenCalled(); }); }); describe('xpack_api_polling_frequency_millis', () => { it('should call rename for this renamed config key', () => { const settings = { xpack_api_polling_frequency_millis: 30000 }; - const log = jest.fn(); - transformDeprecations(settings, fromPath, log); + const addDeprecation = jest.fn(); + transformDeprecations(settings, fromPath, addDeprecation); expect(rename).toHaveBeenCalled(); }); }); diff --git a/x-pack/plugins/monitoring/server/deprecations.ts b/x-pack/plugins/monitoring/server/deprecations.ts index a276cfcee0d35f..79b879b3a5f8b1 100644 --- a/x-pack/plugins/monitoring/server/deprecations.ts +++ b/x-pack/plugins/monitoring/server/deprecations.ts @@ -44,41 +44,41 @@ export const deprecations = ({ 'monitoring.ui.elasticsearch.logFetchCount' ), renameFromRoot('xpack.monitoring', 'monitoring'), - (config, fromPath, logger) => { + (config, fromPath, addDeprecation) => { const emailNotificationsEnabled = get(config, 'cluster_alerts.email_notifications.enabled'); if (emailNotificationsEnabled && !get(config, CLUSTER_ALERTS_ADDRESS_CONFIG_KEY)) { - logger( - `Config key [${fromPath}.${CLUSTER_ALERTS_ADDRESS_CONFIG_KEY}] will be required for email notifications to work in 7.0."` - ); + addDeprecation({ + message: `Config key [${fromPath}.${CLUSTER_ALERTS_ADDRESS_CONFIG_KEY}] will be required for email notifications to work in 7.0."`, + }); } return config; }, - (config, fromPath, logger) => { + (config, fromPath, addDeprecation) => { const es: Record = get(config, 'elasticsearch'); if (es) { if (es.username === 'elastic') { - logger( - `Setting [${fromPath}.username] to "elastic" is deprecated. You should use the "kibana_system" user instead.` - ); + addDeprecation({ + message: `Setting [${fromPath}.username] to "elastic" is deprecated. You should use the "kibana_system" user instead.`, + }); } else if (es.username === 'kibana') { - logger( - `Setting [${fromPath}.username] to "kibana" is deprecated. You should use the "kibana_system" user instead.` - ); + addDeprecation({ + message: `Setting [${fromPath}.username] to "kibana" is deprecated. You should use the "kibana_system" user instead.`, + }); } } return config; }, - (config, fromPath, logger) => { + (config, fromPath, addDeprecation) => { const ssl: Record = get(config, 'elasticsearch.ssl'); if (ssl) { if (ssl.key !== undefined && ssl.certificate === undefined) { - logger( - `Setting [${fromPath}.key] without [${fromPath}.certificate] is deprecated. This has no effect, you should use both settings to enable TLS client authentication to Elasticsearch.` - ); + addDeprecation({ + message: `Setting [${fromPath}.key] without [${fromPath}.certificate] is deprecated. This has no effect, you should use both settings to enable TLS client authentication to Elasticsearch.`, + }); } else if (ssl.certificate !== undefined && ssl.key === undefined) { - logger( - `Setting [${fromPath}.certificate] without [${fromPath}.key] is deprecated. This has no effect, you should use both settings to enable TLS client authentication to Elasticsearch.` - ); + addDeprecation({ + message: `Setting [${fromPath}.certificate] without [${fromPath}.key] is deprecated. This has no effect, you should use both settings to enable TLS client authentication to Elasticsearch.`, + }); } } return config; diff --git a/x-pack/plugins/reporting/server/config/index.test.ts b/x-pack/plugins/reporting/server/config/index.test.ts index d7c12937cda7c0..a395cd23288eb3 100644 --- a/x-pack/plugins/reporting/server/config/index.test.ts +++ b/x-pack/plugins/reporting/server/config/index.test.ts @@ -21,7 +21,7 @@ const applyReportingDeprecations = (settings: Record = {}) => { deprecation, path: CONFIG_PATH, })), - (msg) => deprecationMessages.push(msg) + () => ({ message }) => deprecationMessages.push(message) ); return { messages: deprecationMessages, diff --git a/x-pack/plugins/reporting/server/config/index.ts b/x-pack/plugins/reporting/server/config/index.ts index 06975aa85f1e4c..4b97dbc1e2a846 100644 --- a/x-pack/plugins/reporting/server/config/index.ts +++ b/x-pack/plugins/reporting/server/config/index.ts @@ -24,12 +24,12 @@ export const config: PluginConfigDescriptor = { unused('poll.jobCompletionNotifier.intervalErrorMultiplier'), unused('poll.jobsRefresh.intervalErrorMultiplier'), unused('kibanaApp'), - (settings, fromPath, log) => { + (settings, fromPath, addDeprecation) => { const reporting = get(settings, fromPath); if (reporting?.index) { - log( - `"${fromPath}.index" is deprecated. Multitenancy by changing "kibana.index" will not be supported starting in 8.0. See https://ela.st/kbn-remove-legacy-multitenancy for more details` - ); + addDeprecation({ + message: `"${fromPath}.index" is deprecated. Multitenancy by changing "kibana.index" will not be supported starting in 8.0. See https://ela.st/kbn-remove-legacy-multitenancy for more details`, + }); } return settings; }, diff --git a/x-pack/plugins/security/server/config_deprecations.test.ts b/x-pack/plugins/security/server/config_deprecations.test.ts index 2b6ad603e6163b..80173dd42a49e4 100644 --- a/x-pack/plugins/security/server/config_deprecations.test.ts +++ b/x-pack/plugins/security/server/config_deprecations.test.ts @@ -20,7 +20,7 @@ const applyConfigDeprecations = (settings: Record = {}) => { deprecation, path: 'xpack.security', })), - (msg) => deprecationMessages.push(msg) + () => ({ message }) => deprecationMessages.push(message) ); return { messages: deprecationMessages, diff --git a/x-pack/plugins/security/server/config_deprecations.ts b/x-pack/plugins/security/server/config_deprecations.ts index a7bb5e09fb919d..eae996fe2a5c0c 100644 --- a/x-pack/plugins/security/server/config_deprecations.ts +++ b/x-pack/plugins/security/server/config_deprecations.ts @@ -22,16 +22,17 @@ export const securityConfigDeprecationProvider: ConfigDeprecationProvider = ({ unused('authorization.legacyFallback.enabled'), unused('authc.saml.maxRedirectURLSize'), // Deprecation warning for the old array-based format of `xpack.security.authc.providers`. - (settings, fromPath, log) => { + (settings, fromPath, addDeprecation) => { if (Array.isArray(settings?.xpack?.security?.authc?.providers)) { - log( - 'Defining `xpack.security.authc.providers` as an array of provider types is deprecated. Use extended `object` format instead.' - ); + addDeprecation({ + message: + 'Defining `xpack.security.authc.providers` as an array of provider types is deprecated. Use extended `object` format instead.', + }); } return settings; }, - (settings, fromPath, log) => { + (settings, fromPath, addDeprecation) => { const hasProviderType = (providerType: string) => { const providers = settings?.xpack?.security?.authc?.providers; if (Array.isArray(providers)) { @@ -44,31 +45,34 @@ export const securityConfigDeprecationProvider: ConfigDeprecationProvider = ({ }; if (hasProviderType('basic') && hasProviderType('token')) { - log( - 'Enabling both `basic` and `token` authentication providers in `xpack.security.authc.providers` is deprecated. Login page will only use `token` provider.' - ); + addDeprecation({ + message: + 'Enabling both `basic` and `token` authentication providers in `xpack.security.authc.providers` is deprecated. Login page will only use `token` provider.', + }); } return settings; }, - (settings, fromPath, log) => { + (settings, fromPath, addDeprecation) => { const samlProviders = (settings?.xpack?.security?.authc?.providers?.saml ?? {}) as Record< string, any >; if (Object.values(samlProviders).find((provider) => !!provider.maxRedirectURLSize)) { - log( - '`xpack.security.authc.providers.saml..maxRedirectURLSize` is deprecated and is no longer used' - ); + addDeprecation({ + message: + '`xpack.security.authc.providers.saml..maxRedirectURLSize` is deprecated and is no longer used', + }); } return settings; }, - (settings, fromPath, log) => { + (settings, fromPath, addDeprecation) => { if (settings?.xpack?.security?.enabled === false) { - log( - 'Disabling the security plugin (`xpack.security.enabled`) will not be supported in the next major version (8.0). ' + - 'To turn off security features, disable them in Elasticsearch instead.' - ); + addDeprecation({ + message: + 'Disabling the security plugin (`xpack.security.enabled`) will not be supported in the next major version (8.0). ' + + 'To turn off security features, disable them in Elasticsearch instead.', + }); } return settings; }, diff --git a/x-pack/plugins/spaces/server/config.test.ts b/x-pack/plugins/spaces/server/config.test.ts index 41c4995b5bcf32..1ce1be0698b1c0 100644 --- a/x-pack/plugins/spaces/server/config.test.ts +++ b/x-pack/plugins/spaces/server/config.test.ts @@ -19,7 +19,7 @@ const applyConfigDeprecations = (settings: Record = {}) => { deprecation, path: '', })), - (msg) => deprecationMessages.push(msg) + () => ({ message }) => deprecationMessages.push(message) ); return { messages: deprecationMessages, diff --git a/x-pack/plugins/spaces/server/config.ts b/x-pack/plugins/spaces/server/config.ts index bc53f43c3e8c41..ed541fda6c292b 100644 --- a/x-pack/plugins/spaces/server/config.ts +++ b/x-pack/plugins/spaces/server/config.ts @@ -24,11 +24,11 @@ export function createConfig$(context: PluginInitializerContext) { return context.config.create>(); } -const disabledDeprecation: ConfigDeprecation = (config, fromPath, log) => { +const disabledDeprecation: ConfigDeprecation = (config, fromPath, addDeprecation) => { if (config.xpack?.spaces?.enabled === false) { - log( - `Disabling the spaces plugin (xpack.spaces.enabled) will not be supported in the next major version (8.0)` - ); + addDeprecation({ + message: `Disabling the spaces plugin (xpack.spaces.enabled) will not be supported in the next major version (8.0)`, + }); } return config; }; diff --git a/x-pack/plugins/task_manager/server/index.test.ts b/x-pack/plugins/task_manager/server/index.test.ts index 470b47b40f67df..3fce5f7bdfdf48 100644 --- a/x-pack/plugins/task_manager/server/index.test.ts +++ b/x-pack/plugins/task_manager/server/index.test.ts @@ -22,7 +22,7 @@ const applyTaskManagerDeprecations = (settings: Record = {}) => deprecation, path: CONFIG_PATH, })), - (msg) => deprecationMessages.push(msg) + () => ({ message }) => deprecationMessages.push(message) ); return { messages: deprecationMessages, diff --git a/x-pack/plugins/task_manager/server/index.ts b/x-pack/plugins/task_manager/server/index.ts index 6d744010757f5f..a34f5a87fddbe1 100644 --- a/x-pack/plugins/task_manager/server/index.ts +++ b/x-pack/plugins/task_manager/server/index.ts @@ -31,17 +31,18 @@ export { export const config: PluginConfigDescriptor = { schema: configSchema, deprecations: () => [ - (settings, fromPath, log) => { + (settings, fromPath, addDeprecation) => { const taskManager = get(settings, fromPath); if (taskManager?.index) { - log( - `"${fromPath}.index" is deprecated. Multitenancy by changing "kibana.index" will not be supported starting in 8.0. See https://ela.st/kbn-remove-legacy-multitenancy for more details` - ); + addDeprecation({ + documentationUrl: 'https://ela.st/kbn-remove-legacy-multitenancy', + message: `"${fromPath}.index" is deprecated. Multitenancy by changing "kibana.index" will not be supported starting in 8.0. See https://ela.st/kbn-remove-legacy-multitenancy for more details`, + }); } if (taskManager?.max_workers > MAX_WORKERS_LIMIT) { - log( - `setting "${fromPath}.max_workers" (${taskManager?.max_workers}) greater than ${MAX_WORKERS_LIMIT} is deprecated. Values greater than ${MAX_WORKERS_LIMIT} will not be supported starting in 8.0.` - ); + addDeprecation({ + message: `setting "${fromPath}.max_workers" (${taskManager?.max_workers}) greater than ${MAX_WORKERS_LIMIT} is deprecated. Values greater than ${MAX_WORKERS_LIMIT} will not be supported starting in 8.0.`, + }); } return settings; }, From 78b16fd5d321bdb20e1c786a4c619ea5152d1d99 Mon Sep 17 00:00:00 2001 From: Diana Derevyankina <54894989+DziyanaDzeraviankina@users.noreply.github.com> Date: Tue, 30 Mar 2021 19:41:52 +0300 Subject: [PATCH 18/32] [TSVB] Show an indicator when using Last Value mode (#91977) * [TSVB] Show an indicator when using Last Value mode * Extended some TSVB types, remove unused translations and do some refactoring * Fix some functional tests and label displaying for Last value * Fix some functional tests and label displaying for Last value * Refactor data_time_range_mode_label and change some types * fix CI * Refactor timeseries_visualization seriesData * Remove unused re export * Replace "href" prop with "onClick" in EuiLink and refactor tooltip content * Change link to text and add pointer style to it * FIx import in kibana_framework_adapter * Remove label for entire time range mode and add an icon for last value mode label * Add action to show last value label for TSVB embeddables * Fix TimeseriesVisParams import * Revert "Add action to show last value label for TSVB embeddables" This reverts commit 15f16d6f726eb187d09160cc9f3eae7a5e2fc411. * Put the "Last value" badge on the top of visualization and add an option to hide it * Fix failing _tsvb_markdown test and refactor timeseries_visualization * Move I18nProvider frim timeseries_visualization to timeseries_vis_renderer * Add condition to hide gear button when entire time range mode is enabled, fix gauge scroll issue * Change text in the popover, add condition to indicator if series data is empty, create migration script to hide last value label for previously created visualizations and a test for that Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Alexey Antonov --- ...{interval_regexp.js => interval_regexp.ts} | 0 .../vis_type_timeseries/common/types.ts | 49 +++++++---- .../vis_type_timeseries/common/vis_schema.ts | 1 + .../application/components/index_pattern.js | 17 +++- .../components/last_value_mode_indicator.tsx | 86 +++++++++++++++++++ .../components/last_value_mode_popover.scss | 7 ++ .../components/last_value_mode_popover.tsx | 59 +++++++++++++ ....js => create_interval_based_formatter.ts} | 12 ++- .../lib/{get_interval.js => get_interval.ts} | 35 +++++--- .../components/timeseries_visualization.scss | 3 + .../components/timeseries_visualization.tsx | 56 +++++++++--- .../components/vis_editor_visualization.js | 61 +------------ .../components/vis_types/timeseries/vis.js | 8 +- .../public/timeseries_vis_renderer.tsx | 37 ++++---- .../vis_type_timeseries/server/index.ts | 2 + .../visualization_migrations.test.ts | 32 +++++++ .../saved_objects/visualization_migrations.ts | 30 ++++++- .../page_objects/visual_builder_page.ts | 2 +- .../lib/adapters/framework/adapter_types.ts | 17 ---- .../framework/kibana_framework_adapter.ts | 4 +- .../metrics/kibana_metrics_adapter.ts | 3 +- .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - 23 files changed, 370 insertions(+), 153 deletions(-) rename src/plugins/vis_type_timeseries/common/{interval_regexp.js => interval_regexp.ts} (100%) create mode 100644 src/plugins/vis_type_timeseries/public/application/components/last_value_mode_indicator.tsx create mode 100644 src/plugins/vis_type_timeseries/public/application/components/last_value_mode_popover.scss create mode 100644 src/plugins/vis_type_timeseries/public/application/components/last_value_mode_popover.tsx rename src/plugins/vis_type_timeseries/public/application/components/lib/{create_xaxis_formatter.js => create_interval_based_formatter.ts} (66%) rename src/plugins/vis_type_timeseries/public/application/components/lib/{get_interval.js => get_interval.ts} (70%) create mode 100644 src/plugins/vis_type_timeseries/public/application/components/timeseries_visualization.scss diff --git a/src/plugins/vis_type_timeseries/common/interval_regexp.js b/src/plugins/vis_type_timeseries/common/interval_regexp.ts similarity index 100% rename from src/plugins/vis_type_timeseries/common/interval_regexp.js rename to src/plugins/vis_type_timeseries/common/interval_regexp.ts diff --git a/src/plugins/vis_type_timeseries/common/types.ts b/src/plugins/vis_type_timeseries/common/types.ts index 1fe6196ad545bb..4aa69be3466084 100644 --- a/src/plugins/vis_type_timeseries/common/types.ts +++ b/src/plugins/vis_type_timeseries/common/types.ts @@ -33,32 +33,43 @@ export interface FetchedIndexPattern { indexPatternString: string | undefined; } +export type TimeseriesVisData = SeriesData | TableData; + +interface TableData { + type: PANEL_TYPES.TABLE; + uiRestrictions: TimeseriesUIRestrictions; + series?: PanelData[]; + pivot_label?: string; +} + +// series data is not fully typed yet +export type SeriesData = { + type: Exclude; + uiRestrictions: TimeseriesUIRestrictions; +} & { + [key: string]: PanelSeries; +}; + +interface PanelSeries { + annotations: { + [key: string]: unknown[]; + }; + id: string; + series: PanelData[]; + error?: unknown; +} + export interface PanelData { id: string; label: string; data: Array<[number, number]>; } -// series data is not fully typed yet -interface SeriesData { - [key: string]: { - annotations: { - [key: string]: unknown[]; - }; - id: string; - series: PanelData[]; - error?: unknown; - }; -} +export const isVisTableData = (data: TimeseriesVisData): data is TableData => + data.type === PANEL_TYPES.TABLE; -export type TimeseriesVisData = SeriesData & { - type: PANEL_TYPES; - uiRestrictions: TimeseriesUIRestrictions; - /** - * series array is responsible only for "table" vis type - */ - series?: unknown[]; -}; +export const isVisSeriesData = (data: TimeseriesVisData): data is SeriesData => + data.type !== PANEL_TYPES.TABLE; export interface SanitizedFieldType { name: string; diff --git a/src/plugins/vis_type_timeseries/common/vis_schema.ts b/src/plugins/vis_type_timeseries/common/vis_schema.ts index 297b021fa9e77a..383b0895935655 100644 --- a/src/plugins/vis_type_timeseries/common/vis_schema.ts +++ b/src/plugins/vis_type_timeseries/common/vis_schema.ts @@ -224,6 +224,7 @@ export const panel = schema.object({ gauge_inner_width: stringOrNumberOptionalNullable, gauge_style: stringOptionalNullable, gauge_max: numberOptionalOrEmptyString, + hide_last_value_indicator: schema.boolean(), id: stringRequired, ignore_global_filters: numberOptional, ignore_global_filter: numberOptional, diff --git a/src/plugins/vis_type_timeseries/public/application/components/index_pattern.js b/src/plugins/vis_type_timeseries/public/application/components/index_pattern.js index 8ec35e03c0aec3..0ad6344ac51b79 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/index_pattern.js +++ b/src/plugins/vis_type_timeseries/public/application/components/index_pattern.js @@ -26,7 +26,8 @@ import { createSelectHandler } from './lib/create_select_handler'; import { createTextHandler } from './lib/create_text_handler'; import { IndexPatternSelect } from './lib/index_pattern_select'; import { YesNo } from './yes_no'; -import { KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; +import { LastValueModePopover } from './last_value_mode_popover'; +import { KBN_FIELD_TYPES } from '../../../../data/public'; import { FormValidationContext } from '../contexts/form_validation_context'; import { isGteInterval, validateReInterval, isAutoInterval } from './lib/get_interval'; import { i18n } from '@kbn/i18n'; @@ -42,6 +43,7 @@ import { UI_SETTINGS } from '../../../../data/common'; const RESTRICT_FIELDS = [KBN_FIELD_TYPES.DATE]; const LEVEL_OF_DETAIL_STEPS = 10; const LEVEL_OF_DETAIL_MIN_BUCKETS = 1; +const HIDE_LAST_VALUE_INDICATOR = 'hide_last_value_indicator'; const validateIntervalValue = (intervalValue) => { const isAutoOrGteInterval = isGteInterval(intervalValue) || isAutoInterval(intervalValue); @@ -129,6 +131,11 @@ export const IndexPattern = ({ updateControlValidity(intervalName, intervalValidation.isValid); }, [intervalName, intervalValidation.isValid, updateControlValidity]); + const toggleIndicatorDisplay = useCallback( + () => onChange({ [HIDE_LAST_VALUE_INDICATOR]: !model.hide_last_value_indicator }), + [model.hide_last_value_indicator, onChange] + ); + return (
{!isTimeSeries && ( @@ -154,6 +161,14 @@ export const IndexPattern = ({ onChange={handleSelectChange(TIME_RANGE_MODE_KEY)} singleSelection={{ asPlainText: true }} isDisabled={disabled} + {...(!isEntireTimeRangeActive(model, isTimeSeries) && { + append: ( + + ), + })} /> diff --git a/src/plugins/vis_type_timeseries/public/application/components/last_value_mode_indicator.tsx b/src/plugins/vis_type_timeseries/public/application/components/last_value_mode_indicator.tsx new file mode 100644 index 00000000000000..4ac52a0a80c977 --- /dev/null +++ b/src/plugins/vis_type_timeseries/public/application/components/last_value_mode_indicator.tsx @@ -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 + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiFlexItem, EuiToolTip, EuiFlexGroup, EuiBadge } from '@elastic/eui'; +import { getUISettings } from '../../services'; +import { convertIntervalIntoUnit, isAutoInterval, isGteInterval } from './lib/get_interval'; +import { createIntervalBasedFormatter } from './lib/create_interval_based_formatter'; +import { PanelData } from '../../../common/types'; + +interface LastValueModeIndicatorProps { + seriesData?: PanelData['data']; + panelInterval: number; + modelInterval: string; +} + +const lastValueLabel = i18n.translate('visTypeTimeseries.lastValueModeIndicator.lastValue', { + defaultMessage: 'Last value', +}); + +export const LastValueModeIndicator = ({ + seriesData, + panelInterval, + modelInterval, +}: LastValueModeIndicatorProps) => { + if (!seriesData?.length) return {lastValueLabel}; + + const dateFormat = getUISettings().get('dateFormat'); + const scaledDataFormat = getUISettings().get('dateFormat:scaled'); + + const getFormattedPanelInterval = () => { + const interval = convertIntervalIntoUnit(panelInterval, false); + return interval && `${interval.unitValue}${interval.unitString}`; + }; + + const formatter = createIntervalBasedFormatter(panelInterval, scaledDataFormat, dateFormat); + const lastBucketDate = formatter(seriesData[seriesData.length - 1][0]); + const formattedPanelInterval = + (isAutoInterval(modelInterval) || isGteInterval(modelInterval)) && getFormattedPanelInterval(); + + const tooltipContent = ( + + + + + {formattedPanelInterval && ( + + + + )} + + ); + + return ( + + {}} + onClickAriaLabel={i18n.translate( + 'visTypeTimeseries.lastValueModeIndicator.lastValueModeBadgeAriaLabel', + { + defaultMessage: 'View last value details', + } + )} + > + {lastValueLabel} + + + ); +}; diff --git a/src/plugins/vis_type_timeseries/public/application/components/last_value_mode_popover.scss b/src/plugins/vis_type_timeseries/public/application/components/last_value_mode_popover.scss new file mode 100644 index 00000000000000..eac3fa86a25672 --- /dev/null +++ b/src/plugins/vis_type_timeseries/public/application/components/last_value_mode_popover.scss @@ -0,0 +1,7 @@ +.tvbLastValueModePopover { + height: auto; +} + +.tvbLastValueModePopoverBody { + width: 360px; +} diff --git a/src/plugins/vis_type_timeseries/public/application/components/last_value_mode_popover.tsx b/src/plugins/vis_type_timeseries/public/application/components/last_value_mode_popover.tsx new file mode 100644 index 00000000000000..4124adb6b93fa7 --- /dev/null +++ b/src/plugins/vis_type_timeseries/public/application/components/last_value_mode_popover.tsx @@ -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 + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import './last_value_mode_popover.scss'; + +import React, { useCallback, useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiButtonIcon, EuiPopover, EuiPopoverTitle, EuiSwitch } from '@elastic/eui'; + +interface LastValueModePopoverProps { + isIndicatorDisplayed: boolean; + toggleIndicatorDisplay: () => void; +} + +export const LastValueModePopover = ({ + isIndicatorDisplayed, + toggleIndicatorDisplay, +}: LastValueModePopoverProps) => { + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const onButtonClick = useCallback(() => setIsPopoverOpen((isOpen) => !isOpen), []); + const closePopover = useCallback(() => setIsPopoverOpen(false), []); + + return ( + + } + isOpen={isPopoverOpen} + closePopover={closePopover} + > +
+ + {i18n.translate('visTypeTimeseries.lastValueModePopover.title', { + defaultMessage: 'Last value options', + })} + + +
+
+ ); +}; diff --git a/src/plugins/vis_type_timeseries/public/application/components/lib/create_xaxis_formatter.js b/src/plugins/vis_type_timeseries/public/application/components/lib/create_interval_based_formatter.ts similarity index 66% rename from src/plugins/vis_type_timeseries/public/application/components/lib/create_xaxis_formatter.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/create_interval_based_formatter.ts index e2eea5281b197a..562aec31a08032 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/lib/create_xaxis_formatter.js +++ b/src/plugins/vis_type_timeseries/public/application/components/lib/create_interval_based_formatter.ts @@ -8,17 +8,21 @@ import moment from 'moment'; -function getFormat(interval, rules = []) { +function getFormat(interval: number, rules: string[][] = []) { for (let i = rules.length - 1; i >= 0; i--) { const rule = rules[i]; - if (!rule[0] || interval >= moment.duration(rule[0])) { + if (!rule[0] || interval >= Number(moment.duration(rule[0]))) { return rule[1]; } } } -export function createXaxisFormatter(interval, rules, dateFormat) { - return (val) => { +export function createIntervalBasedFormatter( + interval: number, + rules: string[][], + dateFormat: string +) { + return (val: moment.MomentInput): string => { return moment(val).format(getFormat(interval, rules) ?? dateFormat); }; } diff --git a/src/plugins/vis_type_timeseries/public/application/components/lib/get_interval.js b/src/plugins/vis_type_timeseries/public/application/components/lib/get_interval.ts similarity index 70% rename from src/plugins/vis_type_timeseries/public/application/components/lib/get_interval.js rename to src/plugins/vis_type_timeseries/public/application/components/lib/get_interval.ts index dc297df39a870d..9ff0d6832d0cd1 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/lib/get_interval.js +++ b/src/plugins/vis_type_timeseries/public/application/components/lib/get_interval.ts @@ -13,6 +13,8 @@ import { search } from '../../../../../../plugins/data/public'; const { parseEsInterval } = search.aggs; import { GTE_INTERVAL_RE } from '../../../../common/interval_regexp'; import { AUTO_INTERVAL } from '../../../../common/constants'; +import { isVisTableData, PanelData, TimeseriesVisData } from '../../../../common/types'; +import { TimeseriesVisParams } from '../../../types'; export const unitLookup = { s: i18n.translate('visTypeTimeseries.getInterval.secondsLabel', { defaultMessage: 'seconds' }), @@ -24,9 +26,11 @@ export const unitLookup = { y: i18n.translate('visTypeTimeseries.getInterval.yearsLabel', { defaultMessage: 'years' }), }; -export const convertIntervalIntoUnit = (interval, hasTranslateUnitString = true) => { +type TimeUnit = keyof typeof unitLookup; + +export const convertIntervalIntoUnit = (interval: number, hasTranslateUnitString = true) => { // Iterate units from biggest to smallest - const units = Object.keys(unitLookup).reverse(); + const units = Object.keys(unitLookup).reverse() as TimeUnit[]; const duration = moment.duration(interval, 'ms'); for (let i = 0; i < units.length; i++) { @@ -41,11 +45,16 @@ export const convertIntervalIntoUnit = (interval, hasTranslateUnitString = true) } }; -export const isGteInterval = (interval) => GTE_INTERVAL_RE.test(interval); -export const isAutoInterval = (interval) => !interval || interval === AUTO_INTERVAL; +export const isGteInterval = (interval: string) => GTE_INTERVAL_RE.test(interval); +export const isAutoInterval = (interval: string) => !interval || interval === AUTO_INTERVAL; + +interface ValidationResult { + isValid: boolean; + errorMessage?: string; +} -export const validateReInterval = (intervalValue) => { - const validationResult = {}; +export const validateReInterval = (intervalValue: string) => { + const validationResult = {} as ValidationResult; try { parseEsInterval(intervalValue); @@ -58,14 +67,12 @@ export const validateReInterval = (intervalValue) => { return validationResult; }; -export const getInterval = (visData, model) => { - let series; - - if (model && model.type === 'table') { - series = get(visData, `series[0].series`, []); - } else { - series = get(visData, `${model.id}.series`, []); - } +export const getInterval = (visData: TimeseriesVisData, model: TimeseriesVisParams) => { + const series = get( + visData, + isVisTableData(visData) ? `series[0].series` : `${model.id}.series`, + [] + ) as PanelData[]; return series.reduce((currentInterval, item) => { if (item.data.length > 1) { diff --git a/src/plugins/vis_type_timeseries/public/application/components/timeseries_visualization.scss b/src/plugins/vis_type_timeseries/public/application/components/timeseries_visualization.scss new file mode 100644 index 00000000000000..d76e8b685248fc --- /dev/null +++ b/src/plugins/vis_type_timeseries/public/application/components/timeseries_visualization.scss @@ -0,0 +1,3 @@ +.tvbLastValueIndicator { + align-self: flex-end; +} diff --git a/src/plugins/vis_type_timeseries/public/application/components/timeseries_visualization.tsx b/src/plugins/vis_type_timeseries/public/application/components/timeseries_visualization.tsx index ac15a788d6dabb..ad4949259cfaf1 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/timeseries_visualization.tsx +++ b/src/plugins/vis_type_timeseries/public/application/components/timeseries_visualization.tsx @@ -6,8 +6,13 @@ * Side Public License, v 1. */ +import './timeseries_visualization.scss'; + import React, { useCallback, useEffect } from 'react'; +import { get } from 'lodash'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; + import { IUiSettingsClient } from 'src/core/public'; import { IInterpreterRenderHandlers } from 'src/plugins/expressions'; import { PersistedState } from 'src/plugins/visualizations/public'; @@ -17,7 +22,12 @@ import { PaletteRegistry } from 'src/plugins/charts/public'; import { ErrorComponent } from './error'; import { TimeseriesVisTypes } from './vis_types'; import { TimeseriesVisParams } from '../../types'; -import { TimeseriesVisData } from '../../../common/types'; +import { isVisSeriesData, TimeseriesVisData } from '../../../common/types'; +import { LastValueModeIndicator } from './last_value_mode_indicator'; +import { getInterval } from './lib/get_interval'; +import { AUTO_INTERVAL } from '../../../common/constants'; +import { TIME_RANGE_DATA_MODES } from '../../../common/timerange_data_modes'; +import { PANEL_TYPES } from '../../../common/panel_types'; interface TimeseriesVisualizationProps { className?: string; @@ -76,7 +86,7 @@ function TimeseriesVisualization({ }); // Show the error panel - const error = visData[model.id]?.error; + const error = isVisSeriesData(visData) && visData[model.id]?.error; if (error) { return (
@@ -87,18 +97,40 @@ function TimeseriesVisualization({ const VisComponent = TimeseriesVisTypes[model.type]; + const isLastValueMode = + !model.time_range_mode || model.time_range_mode === TIME_RANGE_DATA_MODES.LAST_VALUE; + const shouldDisplayLastValueIndicator = + isLastValueMode && !model.hide_last_value_indicator && model.type !== PANEL_TYPES.TIMESERIES; + if (VisComponent) { return ( - + + {shouldDisplayLastValueIndicator && ( + + + + )} + + + + ); } diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_editor_visualization.js b/src/plugins/vis_type_timeseries/public/application/components/vis_editor_visualization.js index bfce125a7ed873..bb264aaacbfbfc 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/vis_editor_visualization.js +++ b/src/plugins/vis_type_timeseries/public/application/components/vis_editor_visualization.js @@ -8,17 +8,8 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; -import { get } from 'lodash'; import { keys, EuiFlexGroup, EuiFlexItem, EuiButton, EuiText, EuiSwitch } from '@elastic/eui'; import { FormattedMessage, injectI18n } from '@kbn/i18n/react'; -import { - getInterval, - convertIntervalIntoUnit, - isAutoInterval, - isGteInterval, -} from './lib/get_interval'; -import { AUTO_INTERVAL } from '../../../common/constants'; -import { PANEL_TYPES } from '../../../common/panel_types'; const MIN_CHART_HEIGHT = 300; @@ -28,7 +19,6 @@ class VisEditorVisualizationUI extends Component { this.state = { height: MIN_CHART_HEIGHT, dragging: false, - panelInterval: 0, }; this._visEl = React.createRef(); @@ -65,18 +55,7 @@ class VisEditorVisualizationUI extends Component { await this._handler.render(this._visEl.current); this.props.eventEmitter.emit('embeddableRendered'); - this._subscription = this._handler.handler.data$.subscribe((data) => { - this.setPanelInterval(data.value.visData); - onDataChange(data.value); - }); - } - - setPanelInterval(visData) { - const panelInterval = getInterval(visData, this.props.model); - - if (this.state.panelInterval !== panelInterval) { - this.setState({ panelInterval }); - } + this._subscription = this._handler.handler.data$.subscribe((data) => onDataChange(data.value)); } /** @@ -98,28 +77,6 @@ class VisEditorVisualizationUI extends Component { } }; - hasShowPanelIntervalValue() { - const type = get(this.props, 'model.type', ''); - const interval = get(this.props, 'model.interval', AUTO_INTERVAL); - - return ( - [ - PANEL_TYPES.METRIC, - PANEL_TYPES.TOP_N, - PANEL_TYPES.GAUGE, - PANEL_TYPES.MARKDOWN, - PANEL_TYPES.TABLE, - ].includes(type) && - (isAutoInterval(interval) || isGteInterval(interval)) - ); - } - - getFormattedPanelInterval() { - const interval = convertIntervalIntoUnit(this.state.panelInterval, false); - - return interval ? `${interval.unitValue}${interval.unitString}` : null; - } - componentWillUnmount() { window.removeEventListener('mousemove', this.handleMouseMove); window.removeEventListener('mouseup', this.handleMouseUp); @@ -154,8 +111,6 @@ class VisEditorVisualizationUI extends Component { style.userSelect = 'none'; } - const panelInterval = this.hasShowPanelIntervalValue() && this.getFormattedPanelInterval(); - let applyMessage = ( - {panelInterval && ( - - -

- -

-
-
- )} -

{applyMessage}

diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/vis.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/vis.js index 5a2fc05817f715..ae3fa4d9dcca4f 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/vis.js +++ b/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/vis.js @@ -19,7 +19,7 @@ import { MarkdownSimple } from '../../../../../../../plugins/kibana_react/public import { replaceVars } from '../../lib/replace_vars'; import { getAxisLabelString } from '../../lib/get_axis_label_string'; import { getInterval } from '../../lib/get_interval'; -import { createXaxisFormatter } from '../../lib/create_xaxis_formatter'; +import { createIntervalBasedFormatter } from '../../lib/create_interval_based_formatter'; import { STACKED_OPTIONS } from '../../../visualizations/constants'; import { getCoreStart } from '../../../../services'; @@ -35,7 +35,11 @@ class TimeseriesVisualization extends Component { dateFormat = this.props.getConfig('dateFormat'); xAxisFormatter = (interval) => (val) => { - const formatter = createXaxisFormatter(interval, this.scaledDataFormat, this.dateFormat); + const formatter = createIntervalBasedFormatter( + interval, + this.scaledDataFormat, + this.dateFormat + ); return formatter(val); }; diff --git a/src/plugins/vis_type_timeseries/public/timeseries_vis_renderer.tsx b/src/plugins/vis_type_timeseries/public/timeseries_vis_renderer.tsx index c314594aa54209..208d1b3325e3d7 100644 --- a/src/plugins/vis_type_timeseries/public/timeseries_vis_renderer.tsx +++ b/src/plugins/vis_type_timeseries/public/timeseries_vis_renderer.tsx @@ -9,12 +9,13 @@ import React, { lazy } from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; +import { I18nProvider } from '@kbn/i18n/react'; import { IUiSettingsClient } from 'kibana/public'; import type { PersistedState } from '../../visualizations/public'; import { VisualizationContainer } from '../../visualizations/public'; import { ExpressionRenderDefinition } from '../../expressions/common/expression_renderers'; import { TimeseriesRenderValue } from './metrics_fn'; -import { TimeseriesVisData } from '../common/types'; +import { isVisTableData, TimeseriesVisData } from '../common/types'; import { TimeseriesVisParams } from './types'; import { getChartsSetup } from './services'; @@ -24,7 +25,7 @@ const TimeseriesVisualization = lazy( const checkIfDataExists = (visData: TimeseriesVisData | {}, model: TimeseriesVisParams) => { if ('type' in visData) { - const data = visData.type === 'table' ? visData.series : visData?.[model.id]?.series; + const data = isVisTableData(visData) ? visData.series : visData?.[model.id]?.series; return Boolean(data?.length); } @@ -46,22 +47,24 @@ export const getTimeseriesVisRenderer: (deps: { const palettesService = await palettes.getPalettes(); render( - - + - , + showNoResult={showNoResult} + > + + + , domNode ); }, diff --git a/src/plugins/vis_type_timeseries/server/index.ts b/src/plugins/vis_type_timeseries/server/index.ts index 3c27713c37500c..f41a6374462dcb 100644 --- a/src/plugins/vis_type_timeseries/server/index.ts +++ b/src/plugins/vis_type_timeseries/server/index.ts @@ -33,3 +33,5 @@ export const config: PluginConfigDescriptor = { export function plugin(initializerContext: PluginInitializerContext) { return new VisTypeTimeseriesPlugin(initializerContext); } + +export { TimeseriesVisData, isVisSeriesData, isVisTableData } from '../common/types'; diff --git a/src/plugins/visualizations/server/saved_objects/visualization_migrations.test.ts b/src/plugins/visualizations/server/saved_objects/visualization_migrations.test.ts index 6f1fba26b39b30..e7410c7a973436 100644 --- a/src/plugins/visualizations/server/saved_objects/visualization_migrations.test.ts +++ b/src/plugins/visualizations/server/saved_objects/visualization_migrations.test.ts @@ -1921,4 +1921,36 @@ describe('migration visualization', () => { expect(migratedTestDoc).toEqual(expectedDoc); }); }); + + describe('7.13.0 tsvb hide Last value indicator by default', () => { + const migrate = (doc: any) => + visualizationSavedObjectTypeMigrations['7.13.0']( + doc as Parameters[0], + savedObjectMigrationContext + ); + + const createTestDocWithType = (type: string) => ({ + attributes: { + title: 'My Vis', + description: 'This is my super cool vis.', + visState: `{"type":"metrics","params":{"type":"${type}"}}`, + }, + }); + + it('should set hide_last_value_indicator param to true', () => { + const migratedTestDoc = migrate(createTestDocWithType('markdown')); + const hideLastValueIndicator = JSON.parse(migratedTestDoc.attributes.visState).params + .hide_last_value_indicator; + + expect(hideLastValueIndicator).toBeTruthy(); + }); + + it('should ignore timeseries type', () => { + const migratedTestDoc = migrate(createTestDocWithType('timeseries')); + const hideLastValueIndicator = JSON.parse(migratedTestDoc.attributes.visState).params + .hide_last_value_indicator; + + expect(hideLastValueIndicator).toBeUndefined(); + }); + }); }); diff --git a/src/plugins/visualizations/server/saved_objects/visualization_migrations.ts b/src/plugins/visualizations/server/saved_objects/visualization_migrations.ts index ced33318413c54..633442ec55d69f 100644 --- a/src/plugins/visualizations/server/saved_objects/visualization_migrations.ts +++ b/src/plugins/visualizations/server/saved_objects/visualization_migrations.ts @@ -923,6 +923,34 @@ const migrateVislibAreaLineBarTypes: SavedObjectMigrationFn = (doc) => return doc; }; +/** + * [TSVB] Hide Last value indicator by default for all TSVB types except timeseries + */ +const hideTSVBLastValueIndicator: SavedObjectMigrationFn = (doc) => { + try { + const visState = JSON.parse(doc.attributes.visState); + + if (visState && visState.type === 'metrics' && visState.params.type !== 'timeseries') + return { + ...doc, + attributes: { + ...doc.attributes, + visState: JSON.stringify({ + ...visState, + params: { + ...visState.params, + hide_last_value_indicator: true, + }, + }), + }, + }; + } catch (e) { + // Let it go, the data is invalid and we'll leave it as is + } + + return doc; +}; + export const visualizationSavedObjectTypeMigrations = { /** * We need to have this migration twice, once with a version prior to 7.0.0 once with a version @@ -958,5 +986,5 @@ export const visualizationSavedObjectTypeMigrations = { '7.10.0': flow(migrateFilterRatioQuery, removeTSVBSearchSource), '7.11.0': flow(enableDataTableVisToolbar), '7.12.0': flow(migrateVislibAreaLineBarTypes, migrateSchema), - '7.13.0': flow(addSupportOfDualIndexSelectionModeInTSVB), + '7.13.0': flow(addSupportOfDualIndexSelectionModeInTSVB, hideTSVBLastValueIndicator), }; diff --git a/test/functional/page_objects/visual_builder_page.ts b/test/functional/page_objects/visual_builder_page.ts index fbb2b101eb3af5..3ed5d74808fce5 100644 --- a/test/functional/page_objects/visual_builder_page.ts +++ b/test/functional/page_objects/visual_builder_page.ts @@ -154,7 +154,7 @@ export function VisualBuilderPageProvider({ getService, getPageObjects }: FtrPro } public async getMarkdownText(): Promise { - const el = await find.byCssSelector('.tvbEditorVisualization'); + const el = await find.byCssSelector('.tvbVis'); const text = await el.getVisibleText(); return text; } diff --git a/x-pack/plugins/infra/server/lib/adapters/framework/adapter_types.ts b/x-pack/plugins/infra/server/lib/adapters/framework/adapter_types.ts index 542413118b3302..1231a19f80ca25 100644 --- a/x-pack/plugins/infra/server/lib/adapters/framework/adapter_types.ts +++ b/x-pack/plugins/infra/server/lib/adapters/framework/adapter_types.ts @@ -166,23 +166,6 @@ export interface InfraFieldDef { [type: string]: InfraFieldDetails; } -export interface InfraTSVBResponse { - [key: string]: InfraTSVBPanel; -} - -export interface InfraTSVBPanel { - id: string; - series: InfraTSVBSeries[]; -} - -export interface InfraTSVBSeries { - id: string; - label: string; - data: InfraTSVBDataPoint[]; -} - -export type InfraTSVBDataPoint = [number, number]; - export type InfraRouteConfig = { method: RouteMethod; } & RouteConfig; diff --git a/x-pack/plugins/infra/server/lib/adapters/framework/kibana_framework_adapter.ts b/x-pack/plugins/infra/server/lib/adapters/framework/kibana_framework_adapter.ts index 451b2284ba3101..0176361ede66f3 100644 --- a/x-pack/plugins/infra/server/lib/adapters/framework/kibana_framework_adapter.ts +++ b/x-pack/plugins/infra/server/lib/adapters/framework/kibana_framework_adapter.ts @@ -14,7 +14,6 @@ import { TransportRequestParams } from '@elastic/elasticsearch/lib/Transport'; import { estypes } from '@elastic/elasticsearch'; import { InfraRouteConfig, - InfraTSVBResponse, InfraServerPluginSetupDeps, CallWithRequestParams, InfraDatabaseSearchResponse, @@ -34,6 +33,7 @@ import { RequestHandler } from '../../../../../../../src/core/server'; import { InfraConfig } from '../../../plugin'; import type { InfraPluginRequestHandlerContext } from '../../../types'; import { IndexPatternsFetcher, UI_SETTINGS } from '../../../../../../../src/plugins/data/server'; +import { TimeseriesVisData } from '../../../../../../../src/plugins/vis_type_timeseries/server'; export class KibanaFramework { public router: IRouter; @@ -221,7 +221,7 @@ export class KibanaFramework { model: TSVBMetricModel, timerange: { min: number; max: number }, filters: any[] - ): Promise { + ): Promise { const { getVisData } = this.plugins.visTypeTimeseries; if (typeof getVisData !== 'function') { throw new Error('TSVB is not available'); diff --git a/x-pack/plugins/infra/server/lib/adapters/metrics/kibana_metrics_adapter.ts b/x-pack/plugins/infra/server/lib/adapters/metrics/kibana_metrics_adapter.ts index 921634361f4a2d..6009652e2d0b06 100644 --- a/x-pack/plugins/infra/server/lib/adapters/metrics/kibana_metrics_adapter.ts +++ b/x-pack/plugins/infra/server/lib/adapters/metrics/kibana_metrics_adapter.ts @@ -21,6 +21,7 @@ import { import { calculateMetricInterval } from '../../../utils/calculate_metric_interval'; import { CallWithRequestParams, InfraDatabaseSearchResponse } from '../framework'; import type { InfraPluginRequestHandlerContext } from '../../../types'; +import { isVisSeriesData } from '../../../../../../../src/plugins/vis_type_timeseries/server'; export class KibanaMetricsAdapter implements InfraMetricsAdapter { private framework: KibanaFramework; @@ -59,7 +60,7 @@ export class KibanaMetricsAdapter implements InfraMetricsAdapter { return Promise.all(requests) .then((results) => { - return results.map((result) => { + return results.filter(isVisSeriesData).map((result) => { const metricIds = Object.keys(result).filter( (k) => !['type', 'uiRestrictions'].includes(k) ); diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 7f257d37cd36da..f55b6c5217ad99 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -4544,7 +4544,6 @@ "visTypeTimeseries.visEditorVisualization.changesHaveNotBeenAppliedMessage": "ビジュアライゼーションへの変更が適用されました。", "visTypeTimeseries.visEditorVisualization.changesSuccessfullyAppliedMessage": "最新の変更が適用されました。", "visTypeTimeseries.visEditorVisualization.changesWillBeAutomaticallyAppliedMessage": "変更が自動的に適用されます。", - "visTypeTimeseries.visEditorVisualization.panelInterval": "間隔:{panelInterval}", "visTypeTimeseries.visPicker.gaugeLabel": "ゲージ", "visTypeTimeseries.visPicker.metricLabel": "メトリック", "visTypeTimeseries.visPicker.tableLabel": "表", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 04962e1a563306..c0652b8ac2a653 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -4570,7 +4570,6 @@ "visTypeTimeseries.visEditorVisualization.changesHaveNotBeenAppliedMessage": "尚未应用对此可视化的更改。", "visTypeTimeseries.visEditorVisualization.changesSuccessfullyAppliedMessage": "已应用最新更改。", "visTypeTimeseries.visEditorVisualization.changesWillBeAutomaticallyAppliedMessage": "将自动应用更改。", - "visTypeTimeseries.visEditorVisualization.panelInterval": "时间间隔:{panelInterval}", "visTypeTimeseries.visPicker.gaugeLabel": "仪表盘", "visTypeTimeseries.visPicker.metricLabel": "指标", "visTypeTimeseries.visPicker.tableLabel": "表", From 34b021c4fe36f5931cf82c0c9d44bff22e2a24ae Mon Sep 17 00:00:00 2001 From: Pierre Gayvallet Date: Tue, 30 Mar 2021 18:43:59 +0200 Subject: [PATCH 19/32] use correct config prefix for server config (#95792) --- packages/kbn-cli-dev-mode/src/config/load_config.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/kbn-cli-dev-mode/src/config/load_config.ts b/packages/kbn-cli-dev-mode/src/config/load_config.ts index 46129834ca2d9e..073cd3dbd4b4c8 100644 --- a/packages/kbn-cli-dev-mode/src/config/load_config.ts +++ b/packages/kbn-cli-dev-mode/src/config/load_config.ts @@ -28,13 +28,13 @@ export const loadConfig = async ({ const configService = new ConfigService(rawConfigService, env, logger); configService.setSchema('dev', devConfigSchema); configService.setSchema('plugins', pluginsConfigSchema); - configService.setSchema('http', httpConfigSchema); + configService.setSchema('server', httpConfigSchema); await configService.validate(); const devConfig = configService.atPathSync('dev'); const pluginsConfig = configService.atPathSync('plugins'); - const httpConfig = configService.atPathSync('http'); + const httpConfig = configService.atPathSync('server'); return { dev: new DevConfig(devConfig), From f21417660af7dfc65bea05bf85253bb96c837c87 Mon Sep 17 00:00:00 2001 From: Angela Chuang <6295984+angorayc@users.noreply.github.com> Date: Tue, 30 Mar 2021 19:20:46 +0100 Subject: [PATCH 20/32] [SecuritySolution] Reorganize files for timeline / notes / pinnedEvents (#94358) * re-organize files * fix unit tests * fix unit test * fix types * fix types * fix unit test * reorganize files * update dependency * fix unit test * rename filders * unit test * update prepackaged timelines path * fix integration tests * check if lastSeen is an array * rename Note to Notes Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../influencers/host_to_influencers.test.ts | 3 +- .../public/graphql/introspection.json | 13 +- .../components/first_last_seen_host/index.tsx | 4 +- .../hosts/components/hosts_table/columns.tsx | 9 +- .../handlers/install_prepackaged_rules.ts | 2 +- .../server/graphql/note/resolvers.ts | 4 +- .../server/graphql/pinned_event/resolvers.ts | 2 +- .../server/graphql/timeline/resolvers.ts | 2 +- .../server/lib/compose/kibana.ts | 4 +- .../rules/add_prepackaged_rules_route.test.ts | 4 +- .../rules/add_prepackaged_rules_route.ts | 4 +- ...get_prepackaged_rules_status_route.test.ts | 8 +- .../get_prepackaged_rules_status_route.ts | 8 +- .../scripts/timelines/delete_all_timelines.sh | 2 +- .../__mocks__/create_timelines.ts | 0 .../__mocks__/import_timelines.ts | 2 +- .../__mocks__/prepackaged_timelines.ndjson | 0 .../__mocks__/request_responses.ts | 19 +- .../clean_draft_timelines/index.test.ts} | 21 +- .../clean_draft_timelines/index.ts} | 27 ++- .../get_draft_timelines/index.test.ts} | 21 +- .../get_draft_timelines/index.ts} | 20 +- .../helpers.test.ts} | 22 +- .../install_prepackaged_timelines/helpers.ts} | 11 +- .../index.test.ts} | 26 ++- .../install_prepackaged_timelines/index.ts} | 21 +- .../schemas/check_timelines_status_schema.ts | 20 -- .../create_timelines/helpers.test.ts} | 73 +++++-- .../timelines/create_timelines/helpers.ts | 89 ++++++++ .../create_timelines/index.test.ts} | 58 +++-- .../create_timelines/index.ts} | 22 +- .../export_timelines/helpers.ts} | 16 +- .../export_timelines/index.test.ts} | 24 +- .../export_timelines/index.ts} | 21 +- .../get_timeline/index.test.ts} | 34 +-- .../get_timeline/index.ts} | 29 +-- .../create_timelines_stream_from_ndjson.ts | 8 +- .../get_timelines_from_stream.ts | 4 +- .../import_timelines/helpers.ts} | 54 ++--- .../import_timelines/index.test.ts} | 132 ++++++----- .../import_timelines/index.ts} | 21 +- .../timelines/import_timelines/types.ts | 34 +++ .../patch_timelines/index.test.ts} | 60 +++-- .../patch_timelines/index.ts} | 22 +- .../timeline/routes/utils/create_timelines.ts | 206 ------------------ .../routes/utils/get_timelines_to_install.ts | 21 -- .../routes/utils/get_timelines_to_update.ts | 23 -- .../server/lib/timeline/saved_object/index.ts | 10 + .../notes/get_overridable_note.ts | 39 ++++ .../lib/timeline/saved_object/notes/index.ts | 36 +++ .../saved_object/notes/persist_notes.ts | 38 ++++ .../saved_object/notes}/saved_object.ts | 44 +--- .../saved_object/pinned_events/index.ts} | 40 +++- .../convert_saved_object_to_savedtimeline.ts | 2 +- .../timelines/index.test.ts} | 14 +- .../timelines/index.ts} | 51 ++++- .../timelines}/pick_saved_timeline.test.ts | 4 +- .../timelines}/pick_saved_timeline.ts | 6 +- .../timeline/saved_object_mappings/index.ts | 10 + .../saved_object_mappings/notes.ts} | 4 +- .../saved_object_mappings/pinned_events.ts} | 4 +- .../timelines.ts} | 4 +- .../clean_draft_timelines_schema.ts | 0 .../get_draft_timelines_schema.ts | 0 .../timeline/schemas/draft_timelines/index.ts | 9 + .../schemas.ts => schemas/notes/index.ts} | 1 - .../timeline/schemas/pinned_events/index.ts | 11 + .../timelines}/create_timelines_schema.ts | 0 .../timelines}/export_timelines_schema.ts | 0 .../timelines}/get_timeline_by_id_schema.ts | 0 .../timelines}/import_timelines_schema.ts | 5 +- .../lib/timeline/schemas/timelines/index.ts | 10 + .../timelines/patch_timelines_schema.ts} | 2 +- .../utils/check_timelines_status.ts | 55 ++++- .../lib/timeline/{routes => }/utils/common.ts | 6 +- .../utils/compare_timelines_status.test.ts | 85 ++++---- .../utils/compare_timelines_status.ts | 5 +- .../timeline/{ => utils}/default_timeline.ts | 4 +- .../{ => utils}/default_timeline_headers.ts | 2 +- .../{routes => }/utils/failure_cases.test.ts | 2 +- .../{routes => }/utils/failure_cases.ts | 2 +- .../{routes => }/utils/timeline_object.ts | 11 +- .../security_solution/server/lib/types.ts | 8 +- .../security_solution/server/routes/index.ts | 16 +- .../security_solution/server/saved_objects.ts | 4 +- 85 files changed, 936 insertions(+), 838 deletions(-) rename x-pack/plugins/security_solution/server/lib/timeline/{routes => }/__mocks__/create_timelines.ts (100%) rename x-pack/plugins/security_solution/server/lib/timeline/{routes => }/__mocks__/import_timelines.ts (99%) rename x-pack/plugins/security_solution/server/lib/timeline/{routes => }/__mocks__/prepackaged_timelines.ndjson (100%) rename x-pack/plugins/security_solution/server/lib/timeline/{routes => }/__mocks__/request_responses.ts (95%) rename x-pack/plugins/security_solution/server/lib/timeline/routes/{clean_draft_timelines_route.test.ts => draft_timelines/clean_draft_timelines/index.test.ts} (85%) rename x-pack/plugins/security_solution/server/lib/timeline/routes/{clean_draft_timelines_route.ts => draft_timelines/clean_draft_timelines/index.ts} (75%) rename x-pack/plugins/security_solution/server/lib/timeline/routes/{get_draft_timelines_route.test.ts => draft_timelines/get_draft_timelines/index.test.ts} (84%) rename x-pack/plugins/security_solution/server/lib/timeline/routes/{get_draft_timelines_route.ts => draft_timelines/get_draft_timelines/index.ts} (72%) rename x-pack/plugins/security_solution/server/lib/timeline/routes/{utils/install_prepacked_timelines.test.ts => prepackaged_timelines/install_prepackaged_timelines/helpers.test.ts} (92%) rename x-pack/plugins/security_solution/server/lib/timeline/routes/{utils/install_prepacked_timelines.ts => prepackaged_timelines/install_prepackaged_timelines/helpers.ts} (78%) rename x-pack/plugins/security_solution/server/lib/timeline/routes/{install_prepacked_timelines_route.test.ts => prepackaged_timelines/install_prepackaged_timelines/index.test.ts} (80%) rename x-pack/plugins/security_solution/server/lib/timeline/routes/{install_prepacked_timelines_route.ts => prepackaged_timelines/install_prepackaged_timelines/index.ts} (76%) delete mode 100644 x-pack/plugins/security_solution/server/lib/timeline/routes/schemas/check_timelines_status_schema.ts rename x-pack/plugins/security_solution/server/lib/timeline/routes/{utils/create_timelines.test.ts => timelines/create_timelines/helpers.test.ts} (64%) create mode 100644 x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/helpers.ts rename x-pack/plugins/security_solution/server/lib/timeline/routes/{create_timelines_route.test.ts => timelines/create_timelines/index.test.ts} (80%) rename x-pack/plugins/security_solution/server/lib/timeline/routes/{create_timelines_route.ts => timelines/create_timelines/index.ts} (78%) rename x-pack/plugins/security_solution/server/lib/timeline/routes/{utils/export_timelines.ts => timelines/export_timelines/helpers.ts} (83%) rename x-pack/plugins/security_solution/server/lib/timeline/routes/{export_timelines_route.test.ts => timelines/export_timelines/index.test.ts} (79%) rename x-pack/plugins/security_solution/server/lib/timeline/routes/{export_timelines_route.ts => timelines/export_timelines/index.ts} (75%) rename x-pack/plugins/security_solution/server/lib/timeline/routes/{get_timeline_route.test.ts => timelines/get_timeline/index.test.ts} (62%) rename x-pack/plugins/security_solution/server/lib/timeline/routes/{get_timeline_route.ts => timelines/get_timeline/index.ts} (67%) rename x-pack/plugins/security_solution/server/lib/timeline/{ => routes/timelines/import_timelines}/create_timelines_stream_from_ndjson.ts (84%) rename x-pack/plugins/security_solution/server/lib/timeline/routes/{utils => timelines/import_timelines}/get_timelines_from_stream.ts (91%) rename x-pack/plugins/security_solution/server/lib/timeline/routes/{utils/import_timelines.ts => timelines/import_timelines/helpers.ts} (87%) rename x-pack/plugins/security_solution/server/lib/timeline/routes/{import_timelines_route.test.ts => timelines/import_timelines/index.test.ts} (88%) rename x-pack/plugins/security_solution/server/lib/timeline/routes/{import_timelines_route.ts => timelines/import_timelines/index.ts} (74%) create mode 100644 x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/types.ts rename x-pack/plugins/security_solution/server/lib/timeline/routes/{update_timelines_route.test.ts => timelines/patch_timelines/index.test.ts} (80%) rename x-pack/plugins/security_solution/server/lib/timeline/routes/{update_timelines_route.ts => timelines/patch_timelines/index.ts} (74%) delete mode 100644 x-pack/plugins/security_solution/server/lib/timeline/routes/utils/create_timelines.ts delete mode 100644 x-pack/plugins/security_solution/server/lib/timeline/routes/utils/get_timelines_to_install.ts delete mode 100644 x-pack/plugins/security_solution/server/lib/timeline/routes/utils/get_timelines_to_update.ts create mode 100644 x-pack/plugins/security_solution/server/lib/timeline/saved_object/index.ts create mode 100644 x-pack/plugins/security_solution/server/lib/timeline/saved_object/notes/get_overridable_note.ts create mode 100644 x-pack/plugins/security_solution/server/lib/timeline/saved_object/notes/index.ts create mode 100644 x-pack/plugins/security_solution/server/lib/timeline/saved_object/notes/persist_notes.ts rename x-pack/plugins/security_solution/server/lib/{note => timeline/saved_object/notes}/saved_object.ts (81%) rename x-pack/plugins/security_solution/server/lib/{pinned_event/saved_object.ts => timeline/saved_object/pinned_events/index.ts} (88%) rename x-pack/plugins/security_solution/server/lib/timeline/{ => saved_object/timelines}/convert_saved_object_to_savedtimeline.ts (98%) rename x-pack/plugins/security_solution/server/lib/timeline/{saved_object.test.ts => saved_object/timelines/index.test.ts} (95%) rename x-pack/plugins/security_solution/server/lib/timeline/{saved_object.ts => saved_object/timelines/index.ts} (92%) rename x-pack/plugins/security_solution/server/lib/timeline/{ => saved_object/timelines}/pick_saved_timeline.test.ts (98%) rename x-pack/plugins/security_solution/server/lib/timeline/{ => saved_object/timelines}/pick_saved_timeline.ts (90%) create mode 100644 x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/index.ts rename x-pack/plugins/security_solution/server/lib/{note/saved_object_mappings.ts => timeline/saved_object_mappings/notes.ts} (87%) rename x-pack/plugins/security_solution/server/lib/{pinned_event/saved_object_mappings.ts => timeline/saved_object_mappings/pinned_events.ts} (86%) rename x-pack/plugins/security_solution/server/lib/timeline/{saved_object_mappings.ts => saved_object_mappings/timelines.ts} (98%) rename x-pack/plugins/security_solution/server/lib/timeline/{routes/schemas => schemas/draft_timelines}/clean_draft_timelines_schema.ts (100%) rename x-pack/plugins/security_solution/server/lib/timeline/{routes/schemas => schemas/draft_timelines}/get_draft_timelines_schema.ts (100%) create mode 100644 x-pack/plugins/security_solution/server/lib/timeline/schemas/draft_timelines/index.ts rename x-pack/plugins/security_solution/server/lib/timeline/{routes/schemas/schemas.ts => schemas/notes/index.ts} (87%) create mode 100644 x-pack/plugins/security_solution/server/lib/timeline/schemas/pinned_events/index.ts rename x-pack/plugins/security_solution/server/lib/timeline/{routes/schemas => schemas/timelines}/create_timelines_schema.ts (100%) rename x-pack/plugins/security_solution/server/lib/timeline/{routes/schemas => schemas/timelines}/export_timelines_schema.ts (100%) rename x-pack/plugins/security_solution/server/lib/timeline/{routes/schemas => schemas/timelines}/get_timeline_by_id_schema.ts (100%) rename x-pack/plugins/security_solution/server/lib/timeline/{routes/schemas => schemas/timelines}/import_timelines_schema.ts (93%) create mode 100644 x-pack/plugins/security_solution/server/lib/timeline/schemas/timelines/index.ts rename x-pack/plugins/security_solution/server/lib/timeline/{routes/schemas/update_timelines_schema.ts => schemas/timelines/patch_timelines_schema.ts} (92%) rename x-pack/plugins/security_solution/server/lib/timeline/{routes => }/utils/check_timelines_status.ts (51%) rename x-pack/plugins/security_solution/server/lib/timeline/{routes => }/utils/common.ts (97%) rename x-pack/plugins/security_solution/server/lib/timeline/{routes => }/utils/compare_timelines_status.test.ts (90%) rename x-pack/plugins/security_solution/server/lib/timeline/{routes => }/utils/compare_timelines_status.ts (98%) rename x-pack/plugins/security_solution/server/lib/timeline/{ => utils}/default_timeline.ts (88%) rename x-pack/plugins/security_solution/server/lib/timeline/{ => utils}/default_timeline_headers.ts (93%) rename x-pack/plugins/security_solution/server/lib/timeline/{routes => }/utils/failure_cases.test.ts (99%) rename x-pack/plugins/security_solution/server/lib/timeline/{routes => }/utils/failure_cases.ts (99%) rename x-pack/plugins/security_solution/server/lib/timeline/{routes => }/utils/timeline_object.ts (84%) diff --git a/x-pack/plugins/security_solution/public/common/components/ml/influencers/host_to_influencers.test.ts b/x-pack/plugins/security_solution/public/common/components/ml/influencers/host_to_influencers.test.ts index 5def34a33cd157..310502c04c1b49 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml/influencers/host_to_influencers.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/ml/influencers/host_to_influencers.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { HostItem } from '../../../../graphql/types'; +import { HostItem } from '../../../../../common/search_strategy/security_solution/hosts'; import { InfluencerInput } from '../types'; import { hostToInfluencers } from './host_to_influencers'; @@ -28,6 +28,7 @@ describe('host_to_influencer', () => { test('returns a null if the host.name is null', () => { const hostItem: HostItem = { host: { + // @ts-expect-error name: null, }, }; diff --git a/x-pack/plugins/security_solution/public/graphql/introspection.json b/x-pack/plugins/security_solution/public/graphql/introspection.json index bb18148195cc8f..1df8716ba76e42 100644 --- a/x-pack/plugins/security_solution/public/graphql/introspection.json +++ b/x-pack/plugins/security_solution/public/graphql/introspection.json @@ -1637,7 +1637,18 @@ "isDeprecated": false, "deprecationReason": null }, - { "name": "warning", "description": "", "isDeprecated": false, "deprecationReason": null } + { + "name": "warning", + "description": "", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "unsupported", + "description": "", + "isDeprecated": false, + "deprecationReason": null + } ], "possibleTypes": null }, diff --git a/x-pack/plugins/security_solution/public/hosts/components/first_last_seen_host/index.tsx b/x-pack/plugins/security_solution/public/hosts/components/first_last_seen_host/index.tsx index 540191ac63b6ca..741fc7be6614ff 100644 --- a/x-pack/plugins/security_solution/public/hosts/components/first_last_seen_host/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/components/first_last_seen_host/index.tsx @@ -11,7 +11,7 @@ import React, { useMemo } from 'react'; import { useFirstLastSeenHost } from '../../containers/hosts/first_last_seen'; import { getEmptyTagValue } from '../../../common/components/empty_value'; import { FormattedRelativePreferenceDate } from '../../../common/components/formatted_date'; -import { DocValueFields } from '../../../../common/search_strategy'; +import { Direction, DocValueFields } from '../../../../common/search_strategy'; export enum FirstLastSeenHostType { FIRST_SEEN = 'first-seen', @@ -31,7 +31,7 @@ export const FirstLastSeenHost = React.memo( docValueFields, hostName, indexNames, - order: type === FirstLastSeenHostType.FIRST_SEEN ? 'asc' : 'desc', + order: type === FirstLastSeenHostType.FIRST_SEEN ? Direction.asc : Direction.desc, }); const valueSeen = useMemo( () => (type === FirstLastSeenHostType.FIRST_SEEN ? firstSeen : lastSeen), diff --git a/x-pack/plugins/security_solution/public/hosts/components/hosts_table/columns.tsx b/x-pack/plugins/security_solution/public/hosts/components/hosts_table/columns.tsx index db0dd5e230c871..6f43a18431a275 100644 --- a/x-pack/plugins/security_solution/public/hosts/components/hosts_table/columns.tsx +++ b/x-pack/plugins/security_solution/public/hosts/components/hosts_table/columns.tsx @@ -24,6 +24,7 @@ import { import { HostsTableColumns } from './'; import * as i18n from './translations'; +import { Maybe } from '../../../../common/search_strategy'; export const getHostsColumns = (): HostsTableColumns => [ { @@ -76,9 +77,13 @@ export const getHostsColumns = (): HostsTableColumns => [ truncateText: false, hideForMobile: false, sortable: true, - render: (lastSeen) => { + render: (lastSeen: Maybe | undefined) => { if (lastSeen != null) { - return ; + return ( + + ); } return getEmptyTagValue(); }, diff --git a/x-pack/plugins/security_solution/server/fleet_integration/handlers/install_prepackaged_rules.ts b/x-pack/plugins/security_solution/server/fleet_integration/handlers/install_prepackaged_rules.ts index b317b91b0ccea5..944707d2afb284 100644 --- a/x-pack/plugins/security_solution/server/fleet_integration/handlers/install_prepackaged_rules.ts +++ b/x-pack/plugins/security_solution/server/fleet_integration/handlers/install_prepackaged_rules.ts @@ -12,7 +12,7 @@ import { SecurityPluginSetup } from '../../../../security/server'; import { AppClientFactory } from '../../client'; import { createDetectionIndex } from '../../lib/detection_engine/routes/index/create_index_route'; import { createPrepackagedRules } from '../../lib/detection_engine/routes/rules/add_prepackaged_rules_route'; -import { buildFrameworkRequest } from '../../lib/timeline/routes/utils/common'; +import { buildFrameworkRequest } from '../../lib/timeline/utils/common'; export interface InstallPrepackagedRulesProps { logger: Logger; diff --git a/x-pack/plugins/security_solution/server/graphql/note/resolvers.ts b/x-pack/plugins/security_solution/server/graphql/note/resolvers.ts index 853b1738c4b140..383522bc06bdd7 100644 --- a/x-pack/plugins/security_solution/server/graphql/note/resolvers.ts +++ b/x-pack/plugins/security_solution/server/graphql/note/resolvers.ts @@ -7,7 +7,7 @@ import { AppResolverWithFields, AppResolverOf } from '../../lib/framework'; import { MutationResolvers, QueryResolvers } from '../types'; -import { Note } from '../../lib/note/saved_object'; +import { Notes } from '../../lib/timeline/saved_object/notes'; export type QueryNoteResolver = AppResolverOf; @@ -29,7 +29,7 @@ export type MutationDeleteNoteResolver = AppResolverOf; interface NoteResolversDeps { - note: Note; + note: Notes; } export const createNoteResolvers = ( diff --git a/x-pack/plugins/security_solution/server/graphql/pinned_event/resolvers.ts b/x-pack/plugins/security_solution/server/graphql/pinned_event/resolvers.ts index 7a8ffb313d5c5d..de1a40d9118c7c 100644 --- a/x-pack/plugins/security_solution/server/graphql/pinned_event/resolvers.ts +++ b/x-pack/plugins/security_solution/server/graphql/pinned_event/resolvers.ts @@ -7,7 +7,7 @@ import { AppResolverOf } from '../../lib/framework'; import { MutationResolvers, QueryResolvers } from '../types'; -import { PinnedEvent } from '../../lib/pinned_event/saved_object'; +import { PinnedEvent } from '../../lib/timeline/saved_object/pinned_events'; export type QueryAllPinnedEventsByTimelineIdResolver = AppResolverOf; diff --git a/x-pack/plugins/security_solution/server/graphql/timeline/resolvers.ts b/x-pack/plugins/security_solution/server/graphql/timeline/resolvers.ts index 40264694d2f246..8aa08eda959230 100644 --- a/x-pack/plugins/security_solution/server/graphql/timeline/resolvers.ts +++ b/x-pack/plugins/security_solution/server/graphql/timeline/resolvers.ts @@ -7,7 +7,7 @@ import { AppResolverWithFields, AppResolverOf } from '../../lib/framework'; import { MutationResolvers, QueryResolvers } from '../types'; -import { Timeline } from '../../lib/timeline/saved_object'; +import { Timeline } from '../../lib/timeline/saved_object/timelines'; import { TimelineType } from '../../../common/types/timeline'; export type QueryTimelineResolver = AppResolverOf; diff --git a/x-pack/plugins/security_solution/server/lib/compose/kibana.ts b/x-pack/plugins/security_solution/server/lib/compose/kibana.ts index f5051bd700f00a..5c83f70fdb10b4 100644 --- a/x-pack/plugins/security_solution/server/lib/compose/kibana.ts +++ b/x-pack/plugins/security_solution/server/lib/compose/kibana.ts @@ -16,9 +16,7 @@ import { ElasticsearchIndexFieldAdapter, IndexFields } from '../index_fields'; import { ElasticsearchSourceStatusAdapter, SourceStatus } from '../source_status'; import { ConfigurationSourcesAdapter, Sources } from '../sources'; import { AppBackendLibs, AppDomainLibs } from '../types'; -import * as note from '../note/saved_object'; -import * as pinnedEvent from '../pinned_event/saved_object'; -import * as timeline from '../timeline/saved_object'; +import { note, pinnedEvent, timeline } from '../timeline/saved_object'; import { EndpointAppContext } from '../../endpoint/types'; export function compose( diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.test.ts index cf4b0bcf6f2d99..1195f9e5e1e967 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.test.ts @@ -14,12 +14,12 @@ import { import { requestContextMock, serverMock, createMockConfig, mockGetCurrentUser } from '../__mocks__'; import { AddPrepackagedRulesSchemaDecoded } from '../../../../../common/detection_engine/schemas/request/add_prepackaged_rules_schema'; import { SecurityPluginSetup } from '../../../../../../security/server'; -import { installPrepackagedTimelines } from '../../../timeline/routes/utils/install_prepacked_timelines'; import { addPrepackedRulesRoute, createPrepackagedRules } from './add_prepackaged_rules_route'; import { listMock } from '../../../../../../lists/server/mocks'; import { siemMock } from '../../../../mocks'; import { FrameworkRequest } from '../../../framework'; import { ExceptionListClient } from '../../../../../../lists/server'; +import { installPrepackagedTimelines } from '../../../timeline/routes/prepackaged_timelines/install_prepackaged_timelines'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { elasticsearchClientMock } from 'src/core/server/elasticsearch/client/mocks'; @@ -59,7 +59,7 @@ jest.mock('../../rules/get_prepackaged_rules', () => { }; }); -jest.mock('../../../timeline/routes/utils/install_prepacked_timelines', () => { +jest.mock('../../../timeline/routes/prepackaged_timelines/install_prepackaged_timelines', () => { return { installPrepackagedTimelines: jest.fn().mockResolvedValue({ success: true, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts index e7e571647cbe43..8a8d6925b0e800 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts @@ -22,8 +22,7 @@ import { DETECTION_ENGINE_PREPACKAGED_URL } from '../../../../../common/constant import { ConfigType } from '../../../../config'; import { SetupPlugins } from '../../../../plugin'; -import { buildFrameworkRequest } from '../../../timeline/routes/utils/common'; -import { installPrepackagedTimelines } from '../../../timeline/routes/utils/install_prepacked_timelines'; +import { buildFrameworkRequest } from '../../../timeline/utils/common'; import { getIndexExists } from '../../index/get_index_exists'; import { getPrepackagedRules } from '../../rules/get_prepackaged_rules'; @@ -38,6 +37,7 @@ import { AlertsClient } from '../../../../../../alerting/server'; import { FrameworkRequest } from '../../../framework'; import { ExceptionListClient } from '../../../../../../lists/server'; +import { installPrepackagedTimelines } from '../../../timeline/routes/prepackaged_timelines/install_prepackaged_timelines'; export const addPrepackedRulesRoute = ( router: SecuritySolutionPluginRouter, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/get_prepackaged_rules_status_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/get_prepackaged_rules_status_route.test.ts index 5b59060edf996a..9e843d463ab3e2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/get_prepackaged_rules_status_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/get_prepackaged_rules_status_route.test.ts @@ -15,11 +15,11 @@ import { } from '../__mocks__/request_responses'; import { requestContextMock, serverMock, createMockConfig } from '../__mocks__'; import { SecurityPluginSetup } from '../../../../../../security/server'; -import { checkTimelinesStatus } from '../../../timeline/routes/utils/check_timelines_status'; +import { checkTimelinesStatus } from '../../../timeline/utils/check_timelines_status'; import { mockCheckTimelinesStatusBeforeInstallResult, mockCheckTimelinesStatusAfterInstallResult, -} from '../../../timeline/routes/__mocks__/import_timelines'; +} from '../../../timeline/__mocks__/import_timelines'; jest.mock('../../rules/get_prepackaged_rules', () => { return { @@ -44,8 +44,10 @@ jest.mock('../../rules/get_prepackaged_rules', () => { }; }); -jest.mock('../../../timeline/routes/utils/check_timelines_status', () => { +jest.mock('../../../timeline/utils/check_timelines_status', () => { + const actual = jest.requireActual('../../../timeline/utils/check_timelines_status'); return { + ...actual, checkTimelinesStatus: jest.fn(), }; }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/get_prepackaged_rules_status_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/get_prepackaged_rules_status_route.ts index 6f439bf7394a56..c67f2cb6e9545f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/get_prepackaged_rules_status_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/get_prepackaged_rules_status_route.ts @@ -18,11 +18,13 @@ import { getRulesToInstall } from '../../rules/get_rules_to_install'; import { getRulesToUpdate } from '../../rules/get_rules_to_update'; import { findRules } from '../../rules/find_rules'; import { getExistingPrepackagedRules } from '../../rules/get_existing_prepackaged_rules'; -import { buildFrameworkRequest } from '../../../timeline/routes/utils/common'; +import { buildFrameworkRequest } from '../../../timeline/utils/common'; import { ConfigType } from '../../../../config'; import { SetupPlugins } from '../../../../plugin'; -import { checkTimelinesStatus } from '../../../timeline/routes/utils/check_timelines_status'; -import { checkTimelineStatusRt } from '../../../timeline/routes/schemas/check_timelines_status_schema'; +import { + checkTimelinesStatus, + checkTimelineStatusRt, +} from '../../../timeline/utils/check_timelines_status'; export const getPrepackagedRulesStatusRoute = ( router: SecuritySolutionPluginRouter, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/timelines/delete_all_timelines.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/timelines/delete_all_timelines.sh index 3ddb438a9523f5..559683c2dcce82 100755 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/timelines/delete_all_timelines.sh +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/timelines/delete_all_timelines.sh @@ -9,7 +9,7 @@ set -e ./check_env_variables.sh -# Example: ./timelines/delete_all_timelines.sh +# Example: sh ./timelines/delete_all_timelines.sh curl -s -k \ -H "Content-Type: application/json" \ -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/__mocks__/create_timelines.ts b/x-pack/plugins/security_solution/server/lib/timeline/__mocks__/create_timelines.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/timeline/routes/__mocks__/create_timelines.ts rename to x-pack/plugins/security_solution/server/lib/timeline/__mocks__/create_timelines.ts diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/__mocks__/import_timelines.ts b/x-pack/plugins/security_solution/server/lib/timeline/__mocks__/import_timelines.ts similarity index 99% rename from x-pack/plugins/security_solution/server/lib/timeline/routes/__mocks__/import_timelines.ts rename to x-pack/plugins/security_solution/server/lib/timeline/__mocks__/import_timelines.ts index 777c6a15f9d92f..d7098556c9c3ac 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/__mocks__/import_timelines.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/__mocks__/import_timelines.ts @@ -6,7 +6,7 @@ */ import { omit } from 'lodash/fp'; -import { TimelineId, TimelineType, TimelineStatus } from '../../../../../common/types/timeline'; +import { TimelineId, TimelineType, TimelineStatus } from '../../../../common/types/timeline'; export const mockDuplicateIdErrors = []; diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/__mocks__/prepackaged_timelines.ndjson b/x-pack/plugins/security_solution/server/lib/timeline/__mocks__/prepackaged_timelines.ndjson similarity index 100% rename from x-pack/plugins/security_solution/server/lib/timeline/routes/__mocks__/prepackaged_timelines.ndjson rename to x-pack/plugins/security_solution/server/lib/timeline/__mocks__/prepackaged_timelines.ndjson diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/__mocks__/request_responses.ts b/x-pack/plugins/security_solution/server/lib/timeline/__mocks__/request_responses.ts similarity index 95% rename from x-pack/plugins/security_solution/server/lib/timeline/routes/__mocks__/request_responses.ts rename to x-pack/plugins/security_solution/server/lib/timeline/__mocks__/request_responses.ts index e98a42a6a40f23..2cdcb92baed083 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/__mocks__/request_responses.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/__mocks__/request_responses.ts @@ -14,14 +14,17 @@ import { TIMELINE_IMPORT_URL, TIMELINE_URL, TIMELINE_PREPACKAGED_URL, -} from '../../../../../common/constants'; -import { SavedTimeline, TimelineType, TimelineStatus } from '../../../../../common/types/timeline'; +} from '../../../../common/constants'; +import { SavedTimeline, TimelineType, TimelineStatus } from '../../../../common/types/timeline'; -import { requestMock } from '../../../detection_engine/routes/__mocks__'; +import { requestMock } from '../../detection_engine/routes/__mocks__'; + +import { + patchTimelineSchema, + createTimelineSchema, + GetTimelineByIdSchemaQuery, +} from '../schemas/timelines'; -import { updateTimelineSchema } from '../schemas/update_timelines_schema'; -import { createTimelineSchema } from '../schemas/create_timelines_schema'; -import { GetTimelineByIdSchemaQuery } from '../schemas/get_timeline_by_id_schema'; import { getReadables } from '../utils/common'; export const getExportTimelinesRequest = () => @@ -37,7 +40,7 @@ export const getExportTimelinesRequest = () => }); export const getImportTimelinesRequest = async (fileName?: string) => { - const dir = resolve(join(__dirname, '../../../detection_engine/rules/prepackaged_timelines')); + const dir = resolve(join(__dirname, '../../detection_engine/rules/prepackaged_timelines')); const file = fileName ?? 'index.ndjson'; const dataPath = path.join(dir, file); const readable = await getReadables(dataPath); @@ -147,7 +150,7 @@ export const getCreateTimelinesRequest = (mockBody: rt.TypeOf) => +export const getUpdateTimelinesRequest = (mockBody: rt.TypeOf) => requestMock.create({ method: 'patch', path: TIMELINE_URL, diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/clean_draft_timelines_route.test.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/clean_draft_timelines/index.test.ts similarity index 85% rename from x-pack/plugins/security_solution/server/lib/timeline/routes/clean_draft_timelines_route.test.ts rename to x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/clean_draft_timelines/index.test.ts index de83b9aa10426f..1b230ad0446cef 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/clean_draft_timelines_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/clean_draft_timelines/index.test.ts @@ -5,21 +5,21 @@ * 2.0. */ -import { SecurityPluginSetup } from '../../../../../../plugins/security/server'; -import { TimelineType } from '../../../../common/types/timeline'; +import { SecurityPluginSetup } from '../../../../../../../security/server'; +import { TimelineType } from '../../../../../../common/types/timeline'; import { serverMock, requestContextMock, createMockConfig, -} from '../../detection_engine/routes/__mocks__'; +} from '../../../../detection_engine/routes/__mocks__'; -import { mockGetCurrentUser, mockGetDraftTimelineValue } from './__mocks__/import_timelines'; +import { mockGetCurrentUser, mockGetDraftTimelineValue } from '../../../__mocks__/import_timelines'; import { cleanDraftTimelinesRequest, createTimelineWithTimelineId, -} from './__mocks__/request_responses'; -import { draftTimelineDefaults } from '../default_timeline'; +} from '../../../__mocks__/request_responses'; +import { draftTimelineDefaults } from '../../../utils/default_timeline'; describe('clean draft timelines', () => { let server: ReturnType; @@ -55,7 +55,7 @@ describe('clean draft timelines', () => { mockPersistNote = jest.fn(); mockResetTimeline = jest.fn(); - jest.doMock('../saved_object', () => ({ + jest.doMock('../../../saved_object/timelines', () => ({ getTimeline: mockGetTimeline, getDraftTimeline: mockGetDraftTimeline, resetTimeline: mockResetTimeline, @@ -65,16 +65,15 @@ describe('clean draft timelines', () => { }), })); - jest.doMock('../../pinned_event/saved_object', () => ({ + jest.doMock('../../../saved_object/pinned_events', () => ({ persistPinnedEventOnTimeline: mockPersistPinnedEventOnTimeline, })); - jest.doMock('../../note/saved_object', () => ({ + jest.doMock('../../../saved_object/notes', () => ({ persistNote: mockPersistNote, })); - const cleanDraftTimelinesRoute = jest.requireActual('./clean_draft_timelines_route') - .cleanDraftTimelinesRoute; + const cleanDraftTimelinesRoute = jest.requireActual('./index').cleanDraftTimelinesRoute; cleanDraftTimelinesRoute(server.router, createMockConfig(), securitySetup); }); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/clean_draft_timelines_route.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/clean_draft_timelines/index.ts similarity index 75% rename from x-pack/plugins/security_solution/server/lib/timeline/routes/clean_draft_timelines_route.ts rename to x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/clean_draft_timelines/index.ts index f7385f56b32441..113860f369f783 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/clean_draft_timelines_route.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/clean_draft_timelines/index.ts @@ -6,17 +6,22 @@ */ import uuid from 'uuid'; -import type { SecuritySolutionPluginRouter } from '../../../types'; -import { ConfigType } from '../../..'; -import { transformError, buildSiemResponse } from '../../detection_engine/routes/utils'; -import { TIMELINE_DRAFT_URL } from '../../../../common/constants'; -import { buildFrameworkRequest } from './utils/common'; -import { SetupPlugins } from '../../../plugin'; -import { buildRouteValidationWithExcess } from '../../../utils/build_validation/route_validation'; -import { getDraftTimeline, resetTimeline, getTimeline, persistTimeline } from '../saved_object'; -import { draftTimelineDefaults } from '../default_timeline'; -import { cleanDraftTimelineSchema } from './schemas/clean_draft_timelines_schema'; -import { TimelineType } from '../../../../common/types/timeline'; +import type { SecuritySolutionPluginRouter } from '../../../../../types'; +import { ConfigType } from '../../../../..'; +import { transformError, buildSiemResponse } from '../../../../detection_engine/routes/utils'; +import { TIMELINE_DRAFT_URL } from '../../../../../../common/constants'; +import { buildFrameworkRequest } from '../../../utils/common'; +import { SetupPlugins } from '../../../../../plugin'; +import { buildRouteValidationWithExcess } from '../../../../../utils/build_validation/route_validation'; +import { + getDraftTimeline, + resetTimeline, + getTimeline, + persistTimeline, +} from '../../../saved_object/timelines'; +import { draftTimelineDefaults } from '../../../utils/default_timeline'; +import { cleanDraftTimelineSchema } from '../../../schemas/draft_timelines'; +import { TimelineType } from '../../../../../../common/types/timeline'; export const cleanDraftTimelinesRoute = ( router: SecuritySolutionPluginRouter, diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/get_draft_timelines_route.test.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/get_draft_timelines/index.test.ts similarity index 84% rename from x-pack/plugins/security_solution/server/lib/timeline/routes/get_draft_timelines_route.test.ts rename to x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/get_draft_timelines/index.test.ts index 7bfdbef920ef7d..7e3e2a23222c3a 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/get_draft_timelines_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/get_draft_timelines/index.test.ts @@ -5,21 +5,21 @@ * 2.0. */ -import { SecurityPluginSetup } from '../../../../../../plugins/security/server'; -import { TimelineType } from '../../../../common/types/timeline'; +import { SecurityPluginSetup } from '../../../../../../../security/server'; +import { TimelineType } from '../../../../../../common/types/timeline'; import { serverMock, requestContextMock, createMockConfig, -} from '../../detection_engine/routes/__mocks__'; +} from '../../../../detection_engine/routes/__mocks__'; -import { mockGetCurrentUser, mockGetDraftTimelineValue } from './__mocks__/import_timelines'; +import { mockGetCurrentUser, mockGetDraftTimelineValue } from '../../../__mocks__/import_timelines'; import { getDraftTimelinesRequest, createTimelineWithTimelineId, -} from './__mocks__/request_responses'; -import { draftTimelineDefaults } from '../default_timeline'; +} from '../../../__mocks__/request_responses'; +import { draftTimelineDefaults } from '../../../utils/default_timeline'; describe('get draft timelines', () => { let server: ReturnType; @@ -57,7 +57,7 @@ describe('get draft timelines', () => { describe('Manipulate timeline', () => { describe('Create a new timeline', () => { beforeEach(async () => { - jest.doMock('../saved_object', () => ({ + jest.doMock('../../../saved_object/timelines', () => ({ getTimeline: mockGetTimeline, getDraftTimeline: mockGetDraftTimeline, persistTimeline: mockPersistTimeline.mockReturnValue({ @@ -66,16 +66,15 @@ describe('get draft timelines', () => { }), })); - jest.doMock('../../pinned_event/saved_object', () => ({ + jest.doMock('../../../saved_object/pinned_events', () => ({ persistPinnedEventOnTimeline: mockPersistPinnedEventOnTimeline, })); - jest.doMock('../../note/saved_object', () => ({ + jest.doMock('../../../saved_object/notes', () => ({ persistNote: mockPersistNote, })); - const getDraftTimelinesRoute = jest.requireActual('./get_draft_timelines_route') - .getDraftTimelinesRoute; + const getDraftTimelinesRoute = jest.requireActual('./index').getDraftTimelinesRoute; getDraftTimelinesRoute(server.router, createMockConfig(), securitySetup); }); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/get_draft_timelines_route.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/get_draft_timelines/index.ts similarity index 72% rename from x-pack/plugins/security_solution/server/lib/timeline/routes/get_draft_timelines_route.ts rename to x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/get_draft_timelines/index.ts index 491650bae9605e..f3f813ace411dc 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/get_draft_timelines_route.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/get_draft_timelines/index.ts @@ -5,16 +5,16 @@ * 2.0. */ -import type { SecuritySolutionPluginRouter } from '../../../types'; -import { ConfigType } from '../../..'; -import { transformError, buildSiemResponse } from '../../detection_engine/routes/utils'; -import { TIMELINE_DRAFT_URL } from '../../../../common/constants'; -import { buildFrameworkRequest } from './utils/common'; -import { SetupPlugins } from '../../../plugin'; -import { buildRouteValidationWithExcess } from '../../../utils/build_validation/route_validation'; -import { getDraftTimeline, persistTimeline } from '../saved_object'; -import { draftTimelineDefaults } from '../default_timeline'; -import { getDraftTimelineSchema } from './schemas/get_draft_timelines_schema'; +import type { SecuritySolutionPluginRouter } from '../../../../../types'; +import { ConfigType } from '../../../../..'; +import { transformError, buildSiemResponse } from '../../../../detection_engine/routes/utils'; +import { TIMELINE_DRAFT_URL } from '../../../../../../common/constants'; +import { buildFrameworkRequest } from '../../../utils/common'; +import { SetupPlugins } from '../../../../../plugin'; +import { buildRouteValidationWithExcess } from '../../../../../utils/build_validation/route_validation'; +import { getDraftTimeline, persistTimeline } from '../../../saved_object/timelines'; +import { draftTimelineDefaults } from '../../../utils/default_timeline'; +import { getDraftTimelineSchema } from '../../../schemas/draft_timelines'; export const getDraftTimelinesRoute = ( router: SecuritySolutionPluginRouter, diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/install_prepacked_timelines.test.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/prepackaged_timelines/install_prepackaged_timelines/helpers.test.ts similarity index 92% rename from x-pack/plugins/security_solution/server/lib/timeline/routes/utils/install_prepacked_timelines.test.ts rename to x-pack/plugins/security_solution/server/lib/timeline/routes/prepackaged_timelines/install_prepackaged_timelines/helpers.test.ts index cf025674e9e21b..2b3eda31916a80 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/install_prepacked_timelines.test.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/prepackaged_timelines/install_prepackaged_timelines/helpers.test.ts @@ -8,26 +8,26 @@ import { join, resolve } from 'path'; import { createPromiseFromStreams } from '@kbn/utils'; -import { SecurityPluginSetup } from '../../../../../../security/server'; +import { SecurityPluginSetup } from '../../../../../../../security/server'; -import { FrameworkRequest } from '../../../framework'; +import { FrameworkRequest } from '../../../../framework'; import { createMockConfig, requestContextMock, mockGetCurrentUser, -} from '../../../detection_engine/routes/__mocks__'; +} from '../../../../detection_engine/routes/__mocks__'; import { addPrepackagedRulesRequest, getNonEmptyIndex, getFindResultWithSingleHit, -} from '../../../detection_engine/routes/__mocks__/request_responses'; +} from '../../../../detection_engine/routes/__mocks__/request_responses'; -import * as lib from './install_prepacked_timelines'; -import { importTimelines } from './import_timelines'; -import { buildFrameworkRequest } from './common'; -import { ImportTimelineResultSchema } from '../../../../../common/types/timeline'; +import * as lib from './helpers'; +import { importTimelines } from '../../timelines/import_timelines'; +import { buildFrameworkRequest } from '../../../utils/common'; +import { ImportTimelineResultSchema } from '../../../../../../common/types/timeline'; -jest.mock('./import_timelines'); +jest.mock('../../timelines/import_timelines'); describe('installPrepackagedTimelines', () => { let securitySetup: SecurityPluginSetup; @@ -36,7 +36,7 @@ describe('installPrepackagedTimelines', () => { const { clients, context } = requestContextMock.createTools(); const config = createMockConfig(); - const mockFilePath = '../__mocks__'; + const mockFilePath = '../../../__mocks__'; const mockFileName = 'prepackaged_timelines.ndjson'; beforeEach(async () => { @@ -50,7 +50,7 @@ describe('installPrepackagedTimelines', () => { clients.clusterClient.callAsCurrentUser.mockResolvedValue(getNonEmptyIndex()); clients.alertsClient.find.mockResolvedValue(getFindResultWithSingleHit()); - jest.doMock('./install_prepacked_timelines', () => { + jest.doMock('./helpers', () => { return { ...lib, installPrepackagedTimelines: spyInstallPrepackagedTimelines, diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/install_prepacked_timelines.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/prepackaged_timelines/install_prepackaged_timelines/helpers.ts similarity index 78% rename from x-pack/plugins/security_solution/server/lib/timeline/routes/utils/install_prepacked_timelines.ts rename to x-pack/plugins/security_solution/server/lib/timeline/routes/prepackaged_timelines/install_prepackaged_timelines/helpers.ts index f402d2a4d7f036..c2ff89ee1d2adf 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/install_prepacked_timelines.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/prepackaged_timelines/install_prepackaged_timelines/helpers.ts @@ -8,13 +8,13 @@ import path, { join, resolve } from 'path'; import { Readable } from 'stream'; -import { ImportTimelineResultSchema } from '../../../../../common/types/timeline'; +import { ImportTimelineResultSchema } from '../../../../../../common/types/timeline'; -import { FrameworkRequest } from '../../../framework'; +import { FrameworkRequest } from '../../../../framework'; -import { importTimelines } from './import_timelines'; +import { importTimelines } from '../../timelines/import_timelines'; -import { loadData, getReadables } from './common'; +import { loadData, getReadables } from '../../../utils/common'; export const installPrepackagedTimelines = async ( maxTimelineImportExportSize: number, @@ -25,7 +25,7 @@ export const installPrepackagedTimelines = async ( ): Promise => { let readStream; const dir = resolve( - join(__dirname, filePath ?? '../../../detection_engine/rules/prepackaged_timelines') + join(__dirname, filePath ?? '../../../../detection_engine/rules/prepackaged_timelines') ); const file = fileName ?? 'index.ndjson'; const dataPath = path.join(dir, file); @@ -44,7 +44,6 @@ export const installPrepackagedTimelines = async ( ], }; } - return loadData(readStream, (docs: T) => docs instanceof Readable ? importTimelines(docs, maxTimelineImportExportSize, frameworkRequest, isImmutable) diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/install_prepacked_timelines_route.test.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/prepackaged_timelines/install_prepackaged_timelines/index.test.ts similarity index 80% rename from x-pack/plugins/security_solution/server/lib/timeline/routes/install_prepacked_timelines_route.test.ts rename to x-pack/plugins/security_solution/server/lib/timeline/routes/prepackaged_timelines/install_prepackaged_timelines/index.test.ts index 6e26baa9216437..427086f3c4b6c1 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/install_prepacked_timelines_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/prepackaged_timelines/install_prepackaged_timelines/index.test.ts @@ -5,33 +5,37 @@ * 2.0. */ -import { SecurityPluginSetup } from '../../../../../../plugins/security/server'; +import { SecurityPluginSetup } from '../../../../../../../security/server'; import { serverMock, requestContextMock, createMockConfig, -} from '../../detection_engine/routes/__mocks__'; +} from '../../../../detection_engine/routes/__mocks__'; import { mockGetCurrentUser, mockCheckTimelinesStatusBeforeInstallResult, mockCheckTimelinesStatusAfterInstallResult, -} from './__mocks__/import_timelines'; -import { installPrepackedTimelinesRequest } from './__mocks__/request_responses'; +} from '../../../__mocks__/import_timelines'; +import { installPrepackedTimelinesRequest } from '../../../__mocks__/request_responses'; -import { installPrepackagedTimelines } from './utils/install_prepacked_timelines'; -import { checkTimelinesStatus } from './utils/check_timelines_status'; +import { installPrepackagedTimelines } from './helpers'; +import { checkTimelinesStatus } from '../../../utils/check_timelines_status'; -import { installPrepackedTimelinesRoute } from './install_prepacked_timelines_route'; +import { installPrepackedTimelinesRoute } from '.'; -jest.mock('./utils/install_prepacked_timelines', () => ({ +jest.mock('./helpers', () => ({ installPrepackagedTimelines: jest.fn(), })); -jest.mock('./utils/check_timelines_status', () => ({ - checkTimelinesStatus: jest.fn(), -})); +jest.mock('../../../utils/check_timelines_status', () => { + const actual = jest.requireActual('../../../utils/check_timelines_status'); + return { + ...actual, + checkTimelinesStatus: jest.fn(), + }; +}); describe('installPrepackagedTimelines', () => { let server: ReturnType; diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/install_prepacked_timelines_route.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/prepackaged_timelines/install_prepackaged_timelines/index.ts similarity index 76% rename from x-pack/plugins/security_solution/server/lib/timeline/routes/install_prepacked_timelines_route.ts rename to x-pack/plugins/security_solution/server/lib/timeline/routes/prepackaged_timelines/install_prepackaged_timelines/index.ts index 5366f5b2863250..bb447948df24ad 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/install_prepacked_timelines_route.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/prepackaged_timelines/install_prepackaged_timelines/index.ts @@ -5,22 +5,23 @@ * 2.0. */ -import type { SecuritySolutionPluginRouter } from '../../../types'; +import type { SecuritySolutionPluginRouter } from '../../../../../types'; -import { TIMELINE_PREPACKAGED_URL } from '../../../../common/constants'; +import { TIMELINE_PREPACKAGED_URL } from '../../../../../../common/constants'; -import { SetupPlugins } from '../../../plugin'; -import { ConfigType } from '../../../config'; -import { validate } from '../../../../common/validate'; +import { SetupPlugins } from '../../../../../plugin'; +import { ConfigType } from '../../../../../config'; +import { validate } from '../../../../../../common/validate'; -import { buildSiemResponse, transformError } from '../../detection_engine/routes/utils'; +import { buildSiemResponse, transformError } from '../../../../detection_engine/routes/utils'; -import { installPrepackagedTimelines } from './utils/install_prepacked_timelines'; +import { installPrepackagedTimelines } from './helpers'; -import { checkTimelinesStatus } from './utils/check_timelines_status'; +import { checkTimelinesStatus, checkTimelineStatusRt } from '../../../utils/check_timelines_status'; -import { checkTimelineStatusRt } from './schemas/check_timelines_status_schema'; -import { buildFrameworkRequest } from './utils/common'; +import { buildFrameworkRequest } from '../../../utils/common'; + +export { installPrepackagedTimelines } from './helpers'; export const installPrepackedTimelinesRoute = ( router: SecuritySolutionPluginRouter, diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/schemas/check_timelines_status_schema.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/schemas/check_timelines_status_schema.ts deleted file mode 100644 index 8fd38ad80acd22..00000000000000 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/schemas/check_timelines_status_schema.ts +++ /dev/null @@ -1,20 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as rt from 'io-ts'; -import { TimelineSavedToReturnObjectRuntimeType } from '../../../../../common/types/timeline'; - -import { ImportTimelinesSchemaRt } from './import_timelines_schema'; -import { unionWithNullType } from '../../../../../common/utility_types'; - -export const checkTimelineStatusRt = rt.type({ - timelinesToInstall: rt.array(unionWithNullType(ImportTimelinesSchemaRt)), - timelinesToUpdate: rt.array(unionWithNullType(ImportTimelinesSchemaRt)), - prepackagedTimelines: rt.array(unionWithNullType(TimelineSavedToReturnObjectRuntimeType)), -}); - -export type CheckTimelineStatusRt = rt.TypeOf; diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/create_timelines.test.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/helpers.test.ts similarity index 64% rename from x-pack/plugins/security_solution/server/lib/timeline/routes/utils/create_timelines.test.ts rename to x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/helpers.test.ts index d128b2e992f259..f5e5b7dfb8ae9e 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/create_timelines.test.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/helpers.test.ts @@ -5,15 +5,22 @@ * 2.0. */ -import * as module from './create_timelines'; -import { persistTimeline } from '../../saved_object'; -import { persistPinnedEventOnTimeline } from '../../../pinned_event/saved_object'; -import { persistNote, getNote } from '../../../note/saved_object'; -import { FrameworkRequest } from '../../../framework'; -import { SavedTimeline } from '../../../../../common/types/timeline'; -import { mockTemplate, mockTimeline } from '../__mocks__/create_timelines'; - -const frameworkRequest = {} as FrameworkRequest; +import * as module from './helpers'; +import { savePinnedEvents } from '../../../saved_object/pinned_events'; +import { getNote } from '../../../saved_object/notes'; +import { FrameworkRequest } from '../../../../framework'; +import { SavedTimeline } from '../../../../../../common/types/timeline'; +import { mockTemplate, mockTimeline } from '../../../__mocks__/create_timelines'; +import { buildFrameworkRequest } from '../../../utils/common'; +import { SecurityPluginSetup } from '../../../../../../../security/server'; +import { requestContextMock } from '../../../../detection_engine/routes/__mocks__'; +import { + getCreateTimelinesRequest, + createTimelineWithoutTimelineId, +} from '../../../__mocks__/request_responses'; +import { persistTimeline } from '../../../saved_object/timelines'; +import { persistNotes } from '../../../saved_object/notes/persist_notes'; + const template = { ...mockTemplate } as SavedTimeline; const timeline = { ...mockTimeline } as SavedTimeline; const timelineSavedObjectId = null; @@ -24,7 +31,6 @@ const notes = [ ]; const existingNoteIds = undefined; const isImmutable = true; -const newTimelineSavedObjectId = 'eb2781c0-1df5-11eb-8589-2f13958b79f7'; jest.mock('moment', () => { const mockMoment = { @@ -38,7 +44,7 @@ jest.mock('moment', () => { return jest.fn().mockReturnValue(mockMoment); }); -jest.mock('../../saved_object', () => ({ +jest.mock('../../../saved_object/timelines', () => ({ persistTimeline: jest.fn().mockResolvedValue({ timeline: { savedObjectId: 'eb2781c0-1df5-11eb-8589-2f13958b79f7', @@ -47,16 +53,37 @@ jest.mock('../../saved_object', () => ({ }), })); -jest.mock('../../../pinned_event/saved_object', () => ({ - persistPinnedEventOnTimeline: jest.fn(), +jest.mock('../../../saved_object/pinned_events', () => ({ + savePinnedEvents: jest.fn(), })); -jest.mock('../../../note/saved_object', () => ({ +jest.mock('../../../saved_object/notes', () => ({ getNote: jest.fn(), persistNote: jest.fn(), })); +jest.mock('../../../saved_object/notes/persist_notes', () => ({ + persistNotes: jest.fn(), +})); + describe('createTimelines', () => { + let securitySetup: SecurityPluginSetup; + let frameworkRequest: FrameworkRequest; + + beforeAll(async () => { + securitySetup = ({ + authc: { + getCurrentUser: jest.fn(), + }, + authz: {}, + } as unknown) as SecurityPluginSetup; + + const { context } = requestContextMock.createTools(); + const mockRequest = getCreateTimelinesRequest(createTimelineWithoutTimelineId); + + frameworkRequest = await buildFrameworkRequest(context, securitySetup, mockRequest); + }); + describe('create timelines', () => { beforeAll(async () => { await module.createTimelines({ @@ -88,15 +115,19 @@ describe('createTimelines', () => { }); test('savePinnedEvents', () => { - expect((persistPinnedEventOnTimeline as jest.Mock).mock.calls[0][2]).toEqual('123'); + expect((savePinnedEvents as jest.Mock).mock.calls[0][2]).toEqual(['123']); }); - test('saveNotes', () => { - expect((persistNote as jest.Mock).mock.calls[0][3]).toEqual({ - eventId: undefined, - note: 'new note', - timelineId: newTimelineSavedObjectId, - }); + test('persistNotes', () => { + expect((persistNotes as jest.Mock).mock.calls[0][4]).toEqual([ + { + created: 1603885051655, + createdBy: 'elastic', + note: 'new note', + noteId: 'abc', + timelineId: '', + }, + ]); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/helpers.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/helpers.ts new file mode 100644 index 00000000000000..626f3cbed5b778 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/helpers.ts @@ -0,0 +1,89 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { isEmpty } from 'lodash/fp'; + +import moment from 'moment'; +import { timeline as timelineLib, pinnedEvent as pinnedEventLib } from '../../../saved_object'; +import { FrameworkRequest } from '../../../../framework'; +import { SavedTimeline } from '../../../../../../common/types/timeline'; +import { NoteResult, ResponseTimeline } from '../../../../../graphql/types'; +import { persistNotes } from '../../../saved_object/notes/persist_notes'; + +interface CreateTimelineProps { + frameworkRequest: FrameworkRequest; + timeline: SavedTimeline; + timelineSavedObjectId?: string | null; + timelineVersion?: string | null; + overrideNotesOwner?: boolean; + pinnedEventIds?: string[] | null; + notes?: NoteResult[]; + existingNoteIds?: string[]; + isImmutable?: boolean; +} + +/** allow overrideNotesOwner means overriding by current username, + * disallow overrideNotesOwner means keep the original username. + * overrideNotesOwner = false only happens when import timeline templates, + * as we want to keep the original creator for notes + **/ +export const createTimelines = async ({ + frameworkRequest, + timeline, + timelineSavedObjectId = null, + timelineVersion = null, + pinnedEventIds = null, + notes = [], + existingNoteIds = [], + isImmutable, + overrideNotesOwner = true, +}: CreateTimelineProps): Promise => { + const timerangeStart = isImmutable + ? moment().subtract(24, 'hours').toISOString() + : timeline.dateRange?.start; + const timerangeEnd = isImmutable ? moment().toISOString() : timeline.dateRange?.end; + const responseTimeline = await timelineLib.persistTimeline( + frameworkRequest, + timelineSavedObjectId, + timelineVersion, + { ...timeline, dateRange: { start: timerangeStart, end: timerangeEnd } }, + isImmutable + ); + const newTimelineSavedObjectId = responseTimeline.timeline.savedObjectId; + const newTimelineVersion = responseTimeline.timeline.version; + + let myPromises: unknown[] = []; + if (pinnedEventIds != null && !isEmpty(pinnedEventIds)) { + myPromises = [ + ...myPromises, + pinnedEventLib.savePinnedEvents( + frameworkRequest, + timelineSavedObjectId ?? newTimelineSavedObjectId, + pinnedEventIds + ), + ]; + } + if (!isEmpty(notes)) { + myPromises = [ + ...myPromises, + persistNotes( + frameworkRequest, + timelineSavedObjectId ?? newTimelineSavedObjectId, + newTimelineVersion, + existingNoteIds, + notes, + overrideNotesOwner + ), + ]; + } + + if (myPromises.length > 0) { + await Promise.all(myPromises); + } + + return responseTimeline; +}; diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/create_timelines_route.test.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/index.test.ts similarity index 80% rename from x-pack/plugins/security_solution/server/lib/timeline/routes/create_timelines_route.test.ts rename to x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/index.test.ts index 0b029e41378253..995d8549fff94c 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/create_timelines_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/index.test.ts @@ -5,19 +5,19 @@ * 2.0. */ -import { SecurityPluginSetup } from '../../../../../../plugins/security/server'; +import { SecurityPluginSetup } from '../../../../../../../security/server'; import { serverMock, requestContextMock, createMockConfig, -} from '../../detection_engine/routes/__mocks__'; +} from '../../../../detection_engine/routes/__mocks__'; import { mockGetCurrentUser, mockGetTimelineValue, mockGetTemplateTimelineValue, -} from './__mocks__/import_timelines'; +} from '../../../__mocks__/import_timelines'; import { getCreateTimelinesRequest, inputTimeline, @@ -26,11 +26,11 @@ import { createTemplateTimelineWithoutTimelineId, createTemplateTimelineWithTimelineId, updateTemplateTimelineWithTimelineId, -} from './__mocks__/request_responses'; +} from '../../../__mocks__/request_responses'; import { CREATE_TEMPLATE_TIMELINE_ERROR_MESSAGE, CREATE_TIMELINE_ERROR_MESSAGE, -} from './utils/failure_cases'; +} from '../../../utils/failure_cases'; describe('create timelines', () => { let server: ReturnType; @@ -68,7 +68,7 @@ describe('create timelines', () => { describe('Manipulate timeline', () => { describe('Create a new timeline', () => { beforeEach(async () => { - jest.doMock('../saved_object', () => { + jest.doMock('../../../saved_object/timelines', () => { return { getTimeline: mockGetTimeline.mockReturnValue(null), persistTimeline: mockPersistTimeline.mockReturnValue({ @@ -77,20 +77,19 @@ describe('create timelines', () => { }; }); - jest.doMock('../../pinned_event/saved_object', () => { + jest.doMock('../../../saved_object/pinned_events', () => { return { persistPinnedEventOnTimeline: mockPersistPinnedEventOnTimeline, }; }); - jest.doMock('../../note/saved_object', () => { + jest.doMock('../../../saved_object/notes', () => { return { persistNote: mockPersistNote, }; }); - const createTimelinesRoute = jest.requireActual('./create_timelines_route') - .createTimelinesRoute; + const createTimelinesRoute = jest.requireActual('./index').createTimelinesRoute; createTimelinesRoute(server.router, createMockConfig(), securitySetup); const mockRequest = getCreateTimelinesRequest(createTimelineWithoutTimelineId); @@ -132,27 +131,26 @@ describe('create timelines', () => { describe('Import a timeline already exist', () => { beforeEach(() => { - jest.doMock('../saved_object', () => { + jest.doMock('../../../saved_object/timelines', () => { return { - getTimeline: mockGetTimeline.mockReturnValue(mockGetTimelineValue), + getTimelineOrNull: mockGetTimeline.mockReturnValue(mockGetTimelineValue), persistTimeline: mockPersistTimeline, }; }); - jest.doMock('../../pinned_event/saved_object', () => { + jest.doMock('../../../saved_object/pinned_events', () => { return { persistPinnedEventOnTimeline: mockPersistPinnedEventOnTimeline, }; }); - jest.doMock('../../note/saved_object', () => { + jest.doMock('../../../saved_object/notes', () => { return { persistNote: mockPersistNote, }; }); - const createTimelinesRoute = jest.requireActual('./create_timelines_route') - .createTimelinesRoute; + const createTimelinesRoute = jest.requireActual('./index').createTimelinesRoute; createTimelinesRoute(server.router, createMockConfig(), securitySetup); }); @@ -172,29 +170,28 @@ describe('create timelines', () => { describe('Manipulate timeline template', () => { describe('Create a new timeline template', () => { beforeEach(async () => { - jest.doMock('../saved_object', () => { + jest.doMock('../../../saved_object/timelines', () => { return { - getTimeline: mockGetTimeline.mockReturnValue(null), + getTimelineTemplateOrNull: mockGetTimeline.mockReturnValue(null), persistTimeline: mockPersistTimeline.mockReturnValue({ timeline: createTemplateTimelineWithTimelineId, }), }; }); - jest.doMock('../../pinned_event/saved_object', () => { + jest.doMock('../../../saved_object/pinned_events', () => { return { persistPinnedEventOnTimeline: mockPersistPinnedEventOnTimeline, }; }); - jest.doMock('../../note/saved_object', () => { + jest.doMock('../../../saved_object/notes', () => { return { persistNote: mockPersistNote, }; }); - const createTimelinesRoute = jest.requireActual('./create_timelines_route') - .createTimelinesRoute; + const createTimelinesRoute = jest.requireActual('./index').createTimelinesRoute; createTimelinesRoute(server.router, createMockConfig(), securitySetup); const mockRequest = getCreateTimelinesRequest(createTemplateTimelineWithoutTimelineId); @@ -238,30 +235,29 @@ describe('create timelines', () => { describe('Create a timeline template already exist', () => { beforeEach(() => { - jest.doMock('../saved_object', () => { + jest.doMock('../../../saved_object/timelines', () => { return { - getTimeline: mockGetTimeline.mockReturnValue(mockGetTemplateTimelineValue), - getTimelineByTemplateTimelineId: mockGetTemplateTimeline.mockReturnValue({ - timeline: [mockGetTemplateTimelineValue], - }), + getTimelineOrNull: mockGetTimeline.mockReturnValue(mockGetTemplateTimelineValue), + getTimelineTemplateOrNull: mockGetTemplateTimeline.mockReturnValue( + mockGetTemplateTimelineValue + ), persistTimeline: mockPersistTimeline, }; }); - jest.doMock('../../pinned_event/saved_object', () => { + jest.doMock('../../../saved_object/pinned_events', () => { return { persistPinnedEventOnTimeline: mockPersistPinnedEventOnTimeline, }; }); - jest.doMock('../../note/saved_object', () => { + jest.doMock('../../../saved_object/notes', () => { return { persistNote: mockPersistNote, }; }); - const createTimelinesRoute = jest.requireActual('./create_timelines_route') - .createTimelinesRoute; + const createTimelinesRoute = jest.requireActual('./index').createTimelinesRoute; createTimelinesRoute(server.router, createMockConfig(), securitySetup); }); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/create_timelines_route.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/index.ts similarity index 78% rename from x-pack/plugins/security_solution/server/lib/timeline/routes/create_timelines_route.ts rename to x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/index.ts index 4abfe4f7f71454..f35ddf1a76c7db 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/create_timelines_route.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/index.ts @@ -5,24 +5,26 @@ * 2.0. */ -import type { SecuritySolutionPluginRouter } from '../../../types'; +import type { SecuritySolutionPluginRouter } from '../../../../../types'; -import { TIMELINE_URL } from '../../../../common/constants'; +import { TIMELINE_URL } from '../../../../../../common/constants'; -import { ConfigType } from '../../..'; -import { SetupPlugins } from '../../../plugin'; -import { buildRouteValidationWithExcess } from '../../../utils/build_validation/route_validation'; +import { ConfigType } from '../../../../..'; +import { SetupPlugins } from '../../../../../plugin'; +import { buildRouteValidationWithExcess } from '../../../../../utils/build_validation/route_validation'; -import { transformError, buildSiemResponse } from '../../detection_engine/routes/utils'; +import { transformError, buildSiemResponse } from '../../../../detection_engine/routes/utils'; -import { createTimelineSchema } from './schemas/create_timelines_schema'; +import { createTimelineSchema } from '../../../schemas/timelines'; import { buildFrameworkRequest, CompareTimelinesStatus, TimelineStatusActions, -} from './utils/common'; -import { createTimelines } from './utils/create_timelines'; -import { DEFAULT_ERROR } from './utils/failure_cases'; +} from '../../../utils/common'; +import { DEFAULT_ERROR } from '../../../utils/failure_cases'; +import { createTimelines } from './helpers'; + +export * from './helpers'; export const createTimelinesRoute = ( router: SecuritySolutionPluginRouter, diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/export_timelines.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/export_timelines/helpers.ts similarity index 83% rename from x-pack/plugins/security_solution/server/lib/timeline/routes/utils/export_timelines.ts rename to x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/export_timelines/helpers.ts index fd6f262d6fff6b..a33b8be0c2f31b 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/export_timelines.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/export_timelines/helpers.ts @@ -11,17 +11,17 @@ import { ExportedTimelines, ExportedNotes, ExportTimelineNotFoundError, -} from '../../../../../common/types/timeline'; -import { NoteSavedObject } from '../../../../../common/types/timeline/note'; -import { PinnedEventSavedObject } from '../../../../../common/types/timeline/pinned_event'; +} from '../../../../../../common/types/timeline'; +import { NoteSavedObject } from '../../../../../../common/types/timeline/note'; +import { PinnedEventSavedObject } from '../../../../../../common/types/timeline/pinned_event'; -import { transformDataToNdjson } from '../../../../utils/read_stream/create_stream_from_ndjson'; +import { transformDataToNdjson } from '../../../../../utils/read_stream/create_stream_from_ndjson'; -import { FrameworkRequest } from '../../../framework'; -import * as noteLib from '../../../note/saved_object'; -import * as pinnedEventLib from '../../../pinned_event/saved_object'; +import { FrameworkRequest } from '../../../../framework'; +import * as noteLib from '../../../saved_object/notes'; +import * as pinnedEventLib from '../../../saved_object/pinned_events'; -import { getSelectedTimelines } from '../../saved_object'; +import { getSelectedTimelines } from '../../../saved_object/timelines'; const getGlobalEventNotesByTimelineId = (currentNotes: NoteSavedObject[]): ExportedNotes => { const initialNotes: ExportedNotes = { diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/export_timelines_route.test.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/export_timelines/index.test.ts similarity index 79% rename from x-pack/plugins/security_solution/server/lib/timeline/routes/export_timelines_route.test.ts rename to x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/export_timelines/index.test.ts index a021ca5cf1c48d..da6d0059d5738c 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/export_timelines_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/export_timelines/index.test.ts @@ -11,35 +11,35 @@ import { mockTimelinesSavedObjects, mockPinnedEvents, getExportTimelinesRequest, -} from './__mocks__/request_responses'; -import { exportTimelinesRoute } from './export_timelines_route'; +} from '../../../__mocks__/request_responses'; +import { exportTimelinesRoute } from '.'; import { serverMock, requestContextMock, requestMock, createMockConfig, -} from '../../detection_engine/routes/__mocks__'; -import { TIMELINE_EXPORT_URL } from '../../../../common/constants'; -import { convertSavedObjectToSavedNote } from '../../note/saved_object'; -import { convertSavedObjectToSavedPinnedEvent } from '../../pinned_event/saved_object'; -import { convertSavedObjectToSavedTimeline } from '../convert_saved_object_to_savedtimeline'; -import { mockGetCurrentUser } from './__mocks__/import_timelines'; -import { SecurityPluginSetup } from '../../../../../../plugins/security/server'; +} from '../../../../detection_engine/routes/__mocks__'; +import { TIMELINE_EXPORT_URL } from '../../../../../../common/constants'; +import { convertSavedObjectToSavedNote } from '../../../saved_object/notes/saved_object'; +import { convertSavedObjectToSavedPinnedEvent } from '../../../saved_object/pinned_events'; +import { convertSavedObjectToSavedTimeline } from '../../../saved_object/timelines/convert_saved_object_to_savedtimeline'; +import { mockGetCurrentUser } from '../../../__mocks__/import_timelines'; +import { SecurityPluginSetup } from '../../../../../../../security/server'; -jest.mock('../convert_saved_object_to_savedtimeline', () => { +jest.mock('../../../saved_object/timelines/convert_saved_object_to_savedtimeline', () => { return { convertSavedObjectToSavedTimeline: jest.fn(), }; }); -jest.mock('../../note/saved_object', () => { +jest.mock('../../../saved_object/notes/saved_object', () => { return { convertSavedObjectToSavedNote: jest.fn(), getNotesByTimelineId: jest.fn().mockReturnValue([]), }; }); -jest.mock('../../pinned_event/saved_object', () => { +jest.mock('../../../saved_object/pinned_events', () => { return { convertSavedObjectToSavedPinnedEvent: jest.fn(), getAllPinnedEventsByTimelineId: jest.fn().mockReturnValue([]), diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/export_timelines_route.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/export_timelines/index.ts similarity index 75% rename from x-pack/plugins/security_solution/server/lib/timeline/routes/export_timelines_route.ts rename to x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/export_timelines/index.ts index 19ee945836da51..9e1eabc4450bd9 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/export_timelines_route.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/export_timelines/index.ts @@ -5,19 +5,22 @@ * 2.0. */ -import { TIMELINE_EXPORT_URL } from '../../../../common/constants'; -import type { SecuritySolutionPluginRouter } from '../../../types'; -import { ConfigType } from '../../../config'; -import { transformError, buildSiemResponse } from '../../detection_engine/routes/utils'; +import { TIMELINE_EXPORT_URL } from '../../../../../../common/constants'; +import type { SecuritySolutionPluginRouter } from '../../../../../types'; +import { ConfigType } from '../../../../../config'; +import { transformError, buildSiemResponse } from '../../../../detection_engine/routes/utils'; -import { getExportTimelineByObjectIds } from './utils/export_timelines'; import { exportTimelinesQuerySchema, exportTimelinesRequestBodySchema, -} from './schemas/export_timelines_schema'; -import { buildRouteValidationWithExcess } from '../../../utils/build_validation/route_validation'; -import { buildFrameworkRequest } from './utils/common'; -import { SetupPlugins } from '../../../plugin'; +} from '../../../schemas/timelines'; +import { buildRouteValidationWithExcess } from '../../../../../utils/build_validation/route_validation'; +import { buildFrameworkRequest } from '../../../utils/common'; +import { SetupPlugins } from '../../../../../plugin'; + +import { getExportTimelineByObjectIds } from './helpers'; + +export * from './helpers'; export const exportTimelinesRoute = ( router: SecuritySolutionPluginRouter, diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/get_timeline_route.test.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/get_timeline/index.test.ts similarity index 62% rename from x-pack/plugins/security_solution/server/lib/timeline/routes/get_timeline_route.test.ts rename to x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/get_timeline/index.test.ts index 35db918f96c00b..8c559daa93da9c 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/get_timeline_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/get_timeline/index.test.ts @@ -5,28 +5,28 @@ * 2.0. */ -import { SecurityPluginSetup } from '../../../../../../plugins/security/server'; +import { SecurityPluginSetup } from '../../../../../../../security/server'; import { serverMock, requestContextMock, createMockConfig, -} from '../../detection_engine/routes/__mocks__'; -import { getAllTimeline } from '../saved_object'; - -import { mockGetCurrentUser } from './__mocks__/import_timelines'; -import { getTimelineRequest } from './__mocks__/request_responses'; +} from '../../../../detection_engine/routes/__mocks__'; +import { + getTimelineOrNull, + getTimelineTemplateOrNull, + getAllTimeline, +} from '../../../saved_object/timelines'; -import { getTimeline, getTemplateTimeline } from './utils/create_timelines'; -import { getTimelineRoute } from './get_timeline_route'; +import { mockGetCurrentUser } from '../../../__mocks__/import_timelines'; +import { getTimelineRequest } from '../../../__mocks__/request_responses'; -jest.mock('./utils/create_timelines', () => ({ - getTimeline: jest.fn(), - getTemplateTimeline: jest.fn(), -})); +import { getTimelineRoute } from '.'; -jest.mock('../saved_object', () => ({ +jest.mock('../../../saved_object/timelines', () => ({ getAllTimeline: jest.fn(), + getTimelineOrNull: jest.fn(), + getTimelineTemplateOrNull: jest.fn(), })); describe('get timeline', () => { @@ -51,19 +51,19 @@ describe('get timeline', () => { getTimelineRoute(server.router, createMockConfig(), securitySetup); }); - test('should call getTemplateTimeline if templateTimelineId is given', async () => { + test('should call getTimelineTemplateOrNull if templateTimelineId is given', async () => { const templateTimelineId = '123'; await server.inject(getTimelineRequest({ template_timeline_id: templateTimelineId }), context); - expect((getTemplateTimeline as jest.Mock).mock.calls[0][1]).toEqual(templateTimelineId); + expect((getTimelineTemplateOrNull as jest.Mock).mock.calls[0][1]).toEqual(templateTimelineId); }); - test('should call getTimeline if id is given', async () => { + test('should call getTimelineOrNull if id is given', async () => { const id = '456'; await server.inject(getTimelineRequest({ id }), context); - expect((getTimeline as jest.Mock).mock.calls[0][1]).toEqual(id); + expect((getTimelineOrNull as jest.Mock).mock.calls[0][1]).toEqual(id); }); test('should call getAllTimeline if nither templateTimelineId nor id is given', async () => { diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/get_timeline_route.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/get_timeline/index.ts similarity index 67% rename from x-pack/plugins/security_solution/server/lib/timeline/routes/get_timeline_route.ts rename to x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/get_timeline/index.ts index 3cd43af442eb69..f49110d1057652 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/get_timeline_route.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/get_timeline/index.ts @@ -5,21 +5,24 @@ * 2.0. */ -import type { SecuritySolutionPluginRouter } from '../../../types'; +import type { SecuritySolutionPluginRouter } from '../../../../../types'; -import { TIMELINE_URL } from '../../../../common/constants'; +import { TIMELINE_URL } from '../../../../../../common/constants'; -import { ConfigType } from '../../..'; -import { SetupPlugins } from '../../../plugin'; -import { buildRouteValidationWithExcess } from '../../../utils/build_validation/route_validation'; +import { ConfigType } from '../../../../..'; +import { SetupPlugins } from '../../../../../plugin'; +import { buildRouteValidationWithExcess } from '../../../../../utils/build_validation/route_validation'; -import { buildSiemResponse, transformError } from '../../detection_engine/routes/utils'; +import { buildSiemResponse, transformError } from '../../../../detection_engine/routes/utils'; -import { buildFrameworkRequest } from './utils/common'; -import { getTimelineByIdSchemaQuery } from './schemas/get_timeline_by_id_schema'; -import { getTimeline, getTemplateTimeline } from './utils/create_timelines'; -import { getAllTimeline } from '../saved_object'; -import { TimelineStatus } from '../../../../common/types/timeline'; +import { buildFrameworkRequest } from '../../../utils/common'; +import { getTimelineByIdSchemaQuery } from '../../../schemas/timelines'; +import { + getTimelineTemplateOrNull, + getTimelineOrNull, + getAllTimeline, +} from '../../../saved_object/timelines'; +import { TimelineStatus } from '../../../../../../common/types/timeline'; export const getTimelineRoute = ( router: SecuritySolutionPluginRouter, @@ -41,9 +44,9 @@ export const getTimelineRoute = ( const { template_timeline_id: templateTimelineId, id } = query; let res = null; if (templateTimelineId != null && id == null) { - res = await getTemplateTimeline(frameworkRequest, templateTimelineId); + res = await getTimelineTemplateOrNull(frameworkRequest, templateTimelineId); } else if (templateTimelineId == null && id != null) { - res = await getTimeline(frameworkRequest, id); + res = await getTimelineOrNull(frameworkRequest, id); } else if (templateTimelineId == null && id == null) { const tempResult = await getAllTimeline( frameworkRequest, diff --git a/x-pack/plugins/security_solution/server/lib/timeline/create_timelines_stream_from_ndjson.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/create_timelines_stream_from_ndjson.ts similarity index 84% rename from x-pack/plugins/security_solution/server/lib/timeline/create_timelines_stream_from_ndjson.ts rename to x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/create_timelines_stream_from_ndjson.ts index 118f732735526e..1184629e47e874 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/create_timelines_stream_from_ndjson.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/create_timelines_stream_from_ndjson.ts @@ -16,11 +16,11 @@ import { parseNdjsonStrings, filterExportedCounts, createLimitStream, -} from '../../utils/read_stream/create_stream_from_ndjson'; +} from '../../../../../utils/read_stream/create_stream_from_ndjson'; -import { ImportTimelineResponse } from './routes/utils/import_timelines'; -import { ImportTimelinesSchemaRt } from './routes/schemas/import_timelines_schema'; -import { BadRequestError } from '../detection_engine/errors/bad_request_error'; +import { ImportTimelineResponse } from './types'; +import { ImportTimelinesSchemaRt } from '../../../schemas/timelines/import_timelines_schema'; +import { BadRequestError } from '../../../../detection_engine/errors/bad_request_error'; type ErrorFactory = (message: string) => Error; diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/get_timelines_from_stream.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/get_timelines_from_stream.ts similarity index 91% rename from x-pack/plugins/security_solution/server/lib/timeline/routes/utils/get_timelines_from_stream.ts rename to x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/get_timelines_from_stream.ts index 16dc394ac64e92..51b858efa3fee9 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/get_timelines_from_stream.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/get_timelines_from_stream.ts @@ -6,8 +6,8 @@ */ import uuid from 'uuid'; -import { createBulkErrorObject, BulkError } from '../../../detection_engine/routes/utils'; -import { PromiseFromStreams } from './import_timelines'; +import { createBulkErrorObject, BulkError } from '../../../../detection_engine/routes/utils'; +import { PromiseFromStreams } from './types'; export const getTupleDuplicateErrorsAndUniqueTimeline = ( timelines: PromiseFromStreams[], diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/import_timelines.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/helpers.ts similarity index 87% rename from x-pack/plugins/security_solution/server/lib/timeline/routes/utils/import_timelines.ts rename to x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/helpers.ts index 4b4516e1aa1a18..21ff77e1edbdd1 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/import_timelines.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/helpers.ts @@ -11,45 +11,30 @@ import uuid from 'uuid'; import { createPromiseFromStreams } from '@kbn/utils'; import { - TimelineStatus, - SavedTimeline, ImportTimelineResultSchema, importTimelineResultSchema, -} from '../../../../../common/types/timeline'; -import { validate } from '../../../../../common/validate'; -import { NoteResult } from '../../../../graphql/types'; -import { HapiReadableStream } from '../../../detection_engine/rules/types'; -import { createBulkErrorObject, BulkError } from '../../../detection_engine/routes/utils'; -import { createTimelines } from './create_timelines'; -import { FrameworkRequest } from '../../../framework'; -import { createTimelinesStreamFromNdJson } from '../../create_timelines_stream_from_ndjson'; - -import { getTupleDuplicateErrorsAndUniqueTimeline } from './get_timelines_from_stream'; -import { CompareTimelinesStatus } from './compare_timelines_status'; -import { TimelineStatusActions } from './common'; -import { DEFAULT_ERROR } from './failure_cases'; + TimelineStatus, +} from '../../../../../../common/types/timeline'; +import { validate } from '../../../../../../common/validate'; -export type ImportedTimeline = SavedTimeline & { - savedObjectId: string | null; - version: string | null; - pinnedEventIds: string[]; - globalNotes: NoteResult[]; - eventNotes: NoteResult[]; -}; +import { createBulkErrorObject, BulkError } from '../../../../detection_engine/routes/utils'; -export type PromiseFromStreams = ImportedTimeline; +import { createTimelines } from '../create_timelines'; +import { FrameworkRequest } from '../../../../framework'; -interface ImportRegular { - timeline_id: string; - status_code: number; - message?: string; - action: TimelineStatusActions.createViaImport | TimelineStatusActions.updateViaImport; -} +import { CompareTimelinesStatus } from '../../../utils/compare_timelines_status'; +import { TimelineStatusActions } from '../../../utils/common'; +import { DEFAULT_ERROR } from '../../../utils/failure_cases'; +import { createTimelinesStreamFromNdJson } from './create_timelines_stream_from_ndjson'; +import { getTupleDuplicateErrorsAndUniqueTimeline } from './get_timelines_from_stream'; +import { + ImportedTimeline, + ImportRegular, + ImportTimelineResponse, + PromiseFromStreams, +} from './types'; -export type ImportTimelineResponse = ImportRegular | BulkError; -export interface ImportTimelinesRequestParams { - body: { file: HapiReadableStream }; -} +const CHUNK_PARSED_OBJECT_SIZE = 10; export const isImportRegular = ( importTimelineResponse: ImportTimelineResponse @@ -97,8 +82,6 @@ export const setTimeline = ( }; }; -const CHUNK_PARSED_OBJECT_SIZE = 10; - export const importTimelines = async ( file: Readable, maxTimelineImportExportSize: number, @@ -106,6 +89,7 @@ export const importTimelines = async ( isImmutable?: boolean ): Promise => { const readStream = createTimelinesStreamFromNdJson(maxTimelineImportExportSize); + const parsedObjects = await createPromiseFromStreams([file, ...readStream]); const [duplicateIdErrors, uniqueParsedObjects] = getTupleDuplicateErrorsAndUniqueTimeline( diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/import_timelines_route.test.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/index.test.ts similarity index 88% rename from x-pack/plugins/security_solution/server/lib/timeline/routes/import_timelines_route.test.ts rename to x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/index.test.ts index a4557036aaa63c..2f51b23d736765 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/import_timelines_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/index.test.ts @@ -5,16 +5,16 @@ * 2.0. */ -import { getImportTimelinesRequest } from './__mocks__/request_responses'; +import { getImportTimelinesRequest } from '../../../__mocks__/request_responses'; import { serverMock, requestContextMock, requestMock, createMockConfig, -} from '../../detection_engine/routes/__mocks__'; -import { TIMELINE_EXPORT_URL } from '../../../../common/constants'; -import { TimelineStatus, TimelineType } from '../../../../common/types/timeline'; -import { SecurityPluginSetup } from '../../../../../../plugins/security/server'; +} from '../../../../detection_engine/routes/__mocks__'; +import { TIMELINE_EXPORT_URL } from '../../../../../../common/constants'; +import { TimelineStatus, TimelineType } from '../../../../../../common/types/timeline'; +import { SecurityPluginSetup } from '../../../../../../../security/server'; import { mockUniqueParsedObjects, @@ -29,13 +29,13 @@ import { mockCreatedTemplateTimeline, mockGetTemplateTimelineValue, mockCreatedTimeline, -} from './__mocks__/import_timelines'; +} from '../../../__mocks__/import_timelines'; import { TEMPLATE_TIMELINE_VERSION_CONFLICT_MESSAGE, EMPTY_TITLE_ERROR_MESSAGE, NOT_ALLOW_UPDATE_STATUS_ERROR_MESSAGE, NOT_ALLOW_UPDATE_TIMELINE_TYPE_ERROR_MESSAGE, -} from './utils/failure_cases'; +} from '../../../utils/failure_cases'; describe('import timelines', () => { let server: ReturnType; @@ -74,7 +74,7 @@ describe('import timelines', () => { mockGetNote = jest.fn(); mockGetTupleDuplicateErrorsAndUniqueTimeline = jest.fn(); - jest.doMock('../create_timelines_stream_from_ndjson', () => { + jest.doMock('./create_timelines_stream_from_ndjson', () => { return { createTimelinesStreamFromNdJson: jest.fn().mockReturnValue(mockParsedObjects), }; @@ -86,7 +86,7 @@ describe('import timelines', () => { }; }); - jest.doMock('./utils/get_timelines_from_stream', () => { + jest.doMock('./get_timelines_from_stream', () => { return { getTupleDuplicateErrorsAndUniqueTimeline: mockGetTupleDuplicateErrorsAndUniqueTimeline.mockReturnValue( [mockDuplicateIdErrors, mockUniqueParsedObjects] @@ -97,23 +97,23 @@ describe('import timelines', () => { describe('Import a new timeline', () => { beforeEach(() => { - jest.doMock('../saved_object', () => { + jest.doMock('../../../saved_object/timelines', () => { return { - getTimeline: mockGetTimeline.mockReturnValue(null), - getTimelineByTemplateTimelineId: mockGetTemplateTimeline.mockReturnValue(null), + getTimelineOrNull: mockGetTimeline.mockReturnValue(null), + getTimelineTemplateOrNull: mockGetTemplateTimeline.mockReturnValue(null), persistTimeline: mockPersistTimeline.mockReturnValue({ timeline: mockCreatedTimeline, }), }; }); - jest.doMock('../../pinned_event/saved_object', () => { + jest.doMock('../../../saved_object/pinned_events', () => { return { - persistPinnedEventOnTimeline: mockPersistPinnedEventOnTimeline, + savePinnedEvents: mockPersistPinnedEventOnTimeline, }; }); - jest.doMock('../../note/saved_object', () => { + jest.doMock('../../../saved_object/notes/saved_object', () => { return { persistNote: mockPersistNote, getNote: mockGetNote @@ -150,8 +150,7 @@ describe('import timelines', () => { }; }); - const importTimelinesRoute = jest.requireActual('./import_timelines_route') - .importTimelinesRoute; + const importTimelinesRoute = jest.requireActual('./index').importTimelinesRoute; importTimelinesRoute(server.router, createMockConfig(), securitySetup); }); @@ -225,25 +224,19 @@ describe('import timelines', () => { expect(mockPersistPinnedEventOnTimeline).toHaveBeenCalled(); }); - test('should Create a new pinned event without pinnedEventSavedObjectId', async () => { + test('should Create a new pinned event with new timeline id', async () => { const mockRequest = await getImportTimelinesRequest(); await server.inject(mockRequest, context); - expect(mockPersistPinnedEventOnTimeline.mock.calls[0][1]).toBeNull(); + expect(mockPersistPinnedEventOnTimeline.mock.calls[0][1]).toEqual( + mockCreatedTimeline.savedObjectId + ); }); test('should Create a new pinned event with pinnedEventId', async () => { const mockRequest = await getImportTimelinesRequest(); await server.inject(mockRequest, context); expect(mockPersistPinnedEventOnTimeline.mock.calls[0][2]).toEqual( - mockUniqueParsedObjects[0].pinnedEventIds[0] - ); - }); - - test('should Create a new pinned event with new timelineSavedObjectId', async () => { - const mockRequest = await getImportTimelinesRequest(); - await server.inject(mockRequest, context); - expect(mockPersistPinnedEventOnTimeline.mock.calls[0][3]).toEqual( - mockCreatedTimeline.savedObjectId + mockUniqueParsedObjects[0].pinnedEventIds ); }); @@ -355,28 +348,27 @@ describe('import timelines', () => { describe('Import a timeline already exist', () => { beforeEach(() => { - jest.doMock('../saved_object', () => { + jest.doMock('../../../saved_object/timelines', () => { return { - getTimeline: mockGetTimeline.mockReturnValue(mockGetTimelineValue), - getTimelineByTemplateTimelineId: mockGetTemplateTimeline.mockReturnValue(null), + getTimelineOrNull: mockGetTimeline.mockReturnValue(mockGetTimelineValue), + getTimelineTemplateOrNull: mockGetTemplateTimeline.mockReturnValue(null), persistTimeline: mockPersistTimeline, }; }); - jest.doMock('../../pinned_event/saved_object', () => { + jest.doMock('../../../saved_object/pinned_events', () => { return { - persistPinnedEventOnTimeline: mockPersistPinnedEventOnTimeline, + savePinnedEvents: mockPersistPinnedEventOnTimeline, }; }); - jest.doMock('../../note/saved_object', () => { + jest.doMock('../../../saved_object/notes/saved_object', () => { return { persistNote: mockPersistNote, }; }); - const importTimelinesRoute = jest.requireActual('./import_timelines_route') - .importTimelinesRoute; + const importTimelinesRoute = jest.requireActual('./index').importTimelinesRoute; importTimelinesRoute(server.router, createMockConfig(), securitySetup); }); @@ -461,24 +453,24 @@ describe('import timelines', () => { describe('request validation', () => { beforeEach(() => { - jest.doMock('../saved_object', () => { + jest.doMock('../../../saved_object/timelines', () => { return { - getTimeline: mockGetTimeline.mockReturnValue(null), + getTimelineOrNull: mockGetTimeline.mockReturnValue(null), persistTimeline: mockPersistTimeline.mockReturnValue({ timeline: { savedObjectId: '79deb4c0-6bc1-11ea-9999-f5341fb7a189' }, }), }; }); - jest.doMock('../../pinned_event/saved_object', () => { + jest.doMock('../../../saved_object/pinned_events', () => { return { - persistPinnedEventOnTimeline: mockPersistPinnedEventOnTimeline.mockReturnValue( + savePinnedEvents: mockPersistPinnedEventOnTimeline.mockReturnValue( new Error('Test error') ), }; }); - jest.doMock('../../note/saved_object', () => { + jest.doMock('../../../saved_object/notes/saved_object', () => { return { persistNote: mockPersistNote, }; @@ -490,8 +482,7 @@ describe('import timelines', () => { path: TIMELINE_EXPORT_URL, body: { id: 'someId' }, }); - const importTimelinesRoute = jest.requireActual('./import_timelines_route') - .importTimelinesRoute; + const importTimelinesRoute = jest.requireActual('./index').importTimelinesRoute; importTimelinesRoute(server.router, createMockConfig(), securitySetup); const result = server.validate(request); @@ -513,6 +504,8 @@ describe('import timeline templates', () => { let mockPersistTimeline: jest.Mock; let mockPersistPinnedEventOnTimeline: jest.Mock; let mockPersistNote: jest.Mock; + let mockGetNote: jest.Mock; + let mockGetTupleDuplicateErrorsAndUniqueTimeline: jest.Mock; const mockNewTemplateTimelineId = 'new templateTimelineId'; beforeEach(() => { @@ -536,9 +529,10 @@ describe('import timeline templates', () => { mockPersistTimeline = jest.fn(); mockPersistPinnedEventOnTimeline = jest.fn(); mockPersistNote = jest.fn(); + mockGetNote = jest.fn(); mockGetTupleDuplicateErrorsAndUniqueTimeline = jest.fn(); - jest.doMock('../create_timelines_stream_from_ndjson', () => { + jest.doMock('./create_timelines_stream_from_ndjson', () => { return { createTimelinesStreamFromNdJson: jest .fn() @@ -552,7 +546,7 @@ describe('import timeline templates', () => { }; }); - jest.doMock('./utils/get_timelines_from_stream', () => { + jest.doMock('./get_timelines_from_stream', () => { return { getTupleDuplicateErrorsAndUniqueTimeline: mockGetTupleDuplicateErrorsAndUniqueTimeline.mockReturnValue( [mockDuplicateIdErrors, mockUniqueParsedTemplateTimelineObjects] @@ -567,30 +561,30 @@ describe('import timeline templates', () => { describe('Import a new timeline template', () => { beforeEach(() => { - jest.doMock('../saved_object', () => { + jest.doMock('../../../saved_object/timelines', () => { return { - getTimeline: mockGetTimeline.mockReturnValue(null), - getTimelineByTemplateTimelineId: mockGetTemplateTimeline.mockReturnValue(null), + getTimelineOrNull: mockGetTimeline.mockReturnValue(null), + getTimelineTemplateOrNull: mockGetTemplateTimeline.mockReturnValue(null), persistTimeline: mockPersistTimeline.mockReturnValue({ timeline: mockCreatedTemplateTimeline, }), }; }); - jest.doMock('../../pinned_event/saved_object', () => { + jest.doMock('../../../saved_object/pinned_events', () => { return { - persistPinnedEventOnTimeline: mockPersistPinnedEventOnTimeline, + savePinnedEvents: mockPersistPinnedEventOnTimeline, }; }); - jest.doMock('../../note/saved_object', () => { + jest.doMock('../../../saved_object/notes/saved_object', () => { return { persistNote: mockPersistNote, + getNote: mockGetNote.mockResolvedValueOnce(mockUniqueParsedObjects[0].globalNotes[0]), }; }); - const importTimelinesRoute = jest.requireActual('./import_timelines_route') - .importTimelinesRoute; + const importTimelinesRoute = jest.requireActual('./index').importTimelinesRoute; importTimelinesRoute(server.router, createMockConfig(), securitySetup); }); @@ -717,32 +711,31 @@ describe('import timeline templates', () => { describe('Import a timeline template already exist', () => { beforeEach(() => { - jest.doMock('../saved_object', () => { + jest.doMock('../../../saved_object/timelines', () => { return { - getTimeline: mockGetTimeline.mockReturnValue(mockGetTemplateTimelineValue), - getTimelineByTemplateTimelineId: mockGetTemplateTimeline.mockReturnValue({ - timeline: [mockGetTemplateTimelineValue], - }), + getTimelineOrNull: mockGetTimeline.mockReturnValue(mockGetTemplateTimelineValue), + getTimelineTemplateOrNull: mockGetTemplateTimeline.mockReturnValue( + mockGetTemplateTimelineValue + ), persistTimeline: mockPersistTimeline.mockReturnValue({ timeline: mockCreatedTemplateTimeline, }), }; }); - jest.doMock('../../pinned_event/saved_object', () => { + jest.doMock('../../../saved_object/pinned_events', () => { return { - persistPinnedEventOnTimeline: mockPersistPinnedEventOnTimeline, + savePinnedEvents: mockPersistPinnedEventOnTimeline, }; }); - jest.doMock('../../note/saved_object', () => { + jest.doMock('../../../saved_object/notes/saved_object', () => { return { persistNote: mockPersistNote, }; }); - const importTimelinesRoute = jest.requireActual('./import_timelines_route') - .importTimelinesRoute; + const importTimelinesRoute = jest.requireActual('./index').importTimelinesRoute; importTimelinesRoute(server.router, createMockConfig(), securitySetup); }); @@ -889,24 +882,24 @@ describe('import timeline templates', () => { describe('request validation', () => { beforeEach(() => { - jest.doMock('../saved_object', () => { + jest.doMock('../../../saved_object/timelines', () => { return { - getTimeline: mockGetTimeline.mockReturnValue(null), + getTimelineOrNull: mockGetTimeline.mockReturnValue(null), persistTimeline: mockPersistTimeline.mockReturnValue({ timeline: { savedObjectId: '79deb4c0-6bc1-11ea-9999-f5341fb7a189' }, }), }; }); - jest.doMock('../../pinned_event/saved_object', () => { + jest.doMock('../../../saved_object/pinned_events', () => { return { - persistPinnedEventOnTimeline: mockPersistPinnedEventOnTimeline.mockReturnValue( + savePinnedEvents: mockPersistPinnedEventOnTimeline.mockReturnValue( new Error('Test error') ), }; }); - jest.doMock('../../note/saved_object', () => { + jest.doMock('../../../saved_object/notes/saved_object', () => { return { persistNote: mockPersistNote, }; @@ -918,8 +911,7 @@ describe('import timeline templates', () => { path: TIMELINE_EXPORT_URL, body: { id: 'someId' }, }); - const importTimelinesRoute = jest.requireActual('./import_timelines_route') - .importTimelinesRoute; + const importTimelinesRoute = jest.requireActual('./index').importTimelinesRoute; importTimelinesRoute(server.router, createMockConfig(), securitySetup); const result = server.validate(request); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/import_timelines_route.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/index.ts similarity index 74% rename from x-pack/plugins/security_solution/server/lib/timeline/routes/import_timelines_route.ts rename to x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/index.ts index 585231190292ba..603aad16dd9c64 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/import_timelines_route.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/index.ts @@ -8,18 +8,20 @@ import { extname } from 'path'; import { Readable } from 'stream'; -import type { SecuritySolutionPluginRouter } from '../../../types'; +import type { SecuritySolutionPluginRouter } from '../../../../../types'; -import { TIMELINE_IMPORT_URL } from '../../../../common/constants'; +import { TIMELINE_IMPORT_URL } from '../../../../../../common/constants'; -import { SetupPlugins } from '../../../plugin'; -import { ConfigType } from '../../../config'; -import { buildRouteValidationWithExcess } from '../../../utils/build_validation/route_validation'; -import { buildSiemResponse, transformError } from '../../detection_engine/routes/utils'; +import { SetupPlugins } from '../../../../../plugin'; +import { ConfigType } from '../../../../../config'; +import { buildRouteValidationWithExcess } from '../../../../../utils/build_validation/route_validation'; +import { buildSiemResponse, transformError } from '../../../../detection_engine/routes/utils'; -import { importTimelines } from './utils/import_timelines'; -import { ImportTimelinesPayloadSchemaRt } from './schemas/import_timelines_schema'; -import { buildFrameworkRequest } from './utils/common'; +import { importTimelines } from './helpers'; +import { ImportTimelinesPayloadSchemaRt } from '../../../schemas/timelines/import_timelines_schema'; +import { buildFrameworkRequest } from '../../../utils/common'; + +export { importTimelines } from './helpers'; export const importTimelinesRoute = ( router: SecuritySolutionPluginRouter, @@ -58,7 +60,6 @@ export const importTimelinesRoute = ( body: `Invalid file extension ${fileExtension}`, }); } - const frameworkRequest = await buildFrameworkRequest(context, security, request); const res = await importTimelines( diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/types.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/types.ts new file mode 100644 index 00000000000000..cc4221cba10982 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/types.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { BulkError } from '../../../../detection_engine/routes/utils'; + +import { SavedTimeline } from '../../../../../../common/types/timeline'; +import { NoteResult } from '../../../../../graphql/types'; +import { HapiReadableStream } from '../../../../detection_engine/rules/types'; +import { TimelineStatusActions } from '../../../utils/common'; + +export type ImportedTimeline = SavedTimeline & { + savedObjectId: string | null; + version: string | null; + pinnedEventIds: string[]; + globalNotes: NoteResult[]; + eventNotes: NoteResult[]; +}; + +export type PromiseFromStreams = ImportedTimeline; + +export interface ImportRegular { + timeline_id: string; + status_code: number; + message?: string; + action: TimelineStatusActions.createViaImport | TimelineStatusActions.updateViaImport; +} + +export type ImportTimelineResponse = ImportRegular | BulkError; +export interface ImportTimelinesRequestParams { + body: { file: HapiReadableStream }; +} diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/update_timelines_route.test.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/patch_timelines/index.test.ts similarity index 80% rename from x-pack/plugins/security_solution/server/lib/timeline/routes/update_timelines_route.test.ts rename to x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/patch_timelines/index.test.ts index 6a7159bcfb322d..8f583dbcc05a87 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/update_timelines_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/patch_timelines/index.test.ts @@ -5,29 +5,29 @@ * 2.0. */ -import { SecurityPluginSetup } from '../../../../../../plugins/security/server'; +import { SecurityPluginSetup } from '../../../../../../../security/server'; import { serverMock, requestContextMock, createMockConfig, -} from '../../detection_engine/routes/__mocks__'; +} from '../../../../detection_engine/routes/__mocks__'; import { getUpdateTimelinesRequest, inputTimeline, updateTimelineWithTimelineId, updateTemplateTimelineWithTimelineId, -} from './__mocks__/request_responses'; +} from '../../../__mocks__/request_responses'; import { mockGetCurrentUser, mockGetTimelineValue, mockGetTemplateTimelineValue, -} from './__mocks__/import_timelines'; +} from '../../../__mocks__/import_timelines'; import { UPDATE_TIMELINE_ERROR_MESSAGE, UPDATE_TEMPLATE_TIMELINE_ERROR_MESSAGE, -} from './utils/failure_cases'; +} from '../../../utils/failure_cases'; describe('update timelines', () => { let server: ReturnType; @@ -65,29 +65,28 @@ describe('update timelines', () => { describe('Manipulate timeline', () => { describe('Update an existing timeline', () => { beforeEach(async () => { - jest.doMock('../saved_object', () => { + jest.doMock('../../../saved_object/timelines', () => { return { - getTimeline: mockGetTimeline.mockReturnValue(mockGetTimelineValue), + getTimelineOrNull: mockGetTimeline.mockReturnValue(mockGetTimelineValue), persistTimeline: mockPersistTimeline.mockReturnValue({ timeline: updateTimelineWithTimelineId.timeline, }), }; }); - jest.doMock('../../pinned_event/saved_object', () => { + jest.doMock('../../../saved_object/pinned_events', () => { return { persistPinnedEventOnTimeline: mockPersistPinnedEventOnTimeline, }; }); - jest.doMock('../../note/saved_object', () => { + jest.doMock('../../../saved_object/notes', () => { return { persistNote: mockPersistNote, }; }); - const updateTimelinesRoute = jest.requireActual('./update_timelines_route') - .updateTimelinesRoute; + const updateTimelinesRoute = jest.requireActual('./index').updateTimelinesRoute; updateTimelinesRoute(server.router, createMockConfig(), securitySetup); const mockRequest = getUpdateTimelinesRequest(updateTimelineWithTimelineId); @@ -131,28 +130,27 @@ describe('update timelines', () => { describe("Update a timeline that doesn't exist", () => { beforeEach(() => { - jest.doMock('../saved_object', () => { + jest.doMock('../../../saved_object/timelines', () => { return { - getTimeline: mockGetTimeline.mockReturnValue(null), + getTimelineOrNull: mockGetTimeline.mockReturnValue(null), getTimelineByTemplateTimelineId: mockGetTemplateTimeline.mockReturnValue(null), persistTimeline: mockPersistTimeline, }; }); - jest.doMock('../../pinned_event/saved_object', () => { + jest.doMock('../../../saved_object/pinned_events', () => { return { persistPinnedEventOnTimeline: mockPersistPinnedEventOnTimeline, }; }); - jest.doMock('../../note/saved_object', () => { + jest.doMock('../../../saved_object/notes', () => { return { persistNote: mockPersistNote, }; }); - const updateTimelinesRoute = jest.requireActual('./update_timelines_route') - .updateTimelinesRoute; + const updateTimelinesRoute = jest.requireActual('./index').updateTimelinesRoute; updateTimelinesRoute(server.router, createMockConfig(), securitySetup); }); @@ -172,10 +170,10 @@ describe('update timelines', () => { describe('Manipulate timeline template', () => { describe('Update an existing timeline template', () => { beforeEach(async () => { - jest.doMock('../saved_object', () => { + jest.doMock('../../../saved_object/timelines', () => { return { - getTimeline: mockGetTimeline.mockReturnValue(mockGetTemplateTimelineValue), - getTimelineByTemplateTimelineId: mockGetTemplateTimeline.mockReturnValue({ + getTimelineOrNull: mockGetTimeline.mockReturnValue(mockGetTemplateTimelineValue), + getTimelineTemplateOrNull: mockGetTemplateTimeline.mockReturnValue({ timeline: [mockGetTemplateTimelineValue], }), persistTimeline: mockPersistTimeline.mockReturnValue({ @@ -184,20 +182,19 @@ describe('update timelines', () => { }; }); - jest.doMock('../../pinned_event/saved_object', () => { + jest.doMock('../../../saved_object/pinned_events', () => { return { persistPinnedEventOnTimeline: mockPersistPinnedEventOnTimeline, }; }); - jest.doMock('../../note/saved_object', () => { + jest.doMock('../../../saved_object/notes', () => { return { persistNote: mockPersistNote, }; }); - const updateTimelinesRoute = jest.requireActual('./update_timelines_route') - .updateTimelinesRoute; + const updateTimelinesRoute = jest.requireActual('./index').updateTimelinesRoute; updateTimelinesRoute(server.router, createMockConfig(), securitySetup); const mockRequest = getUpdateTimelinesRequest(updateTemplateTimelineWithTimelineId); @@ -253,30 +250,27 @@ describe('update timelines', () => { describe("Update a timeline template that doesn't exist", () => { beforeEach(() => { - jest.doMock('../saved_object', () => { + jest.doMock('../../../saved_object/timelines', () => { return { - getTimeline: mockGetTimeline.mockReturnValue(null), - getTimelineByTemplateTimelineId: mockGetTemplateTimeline.mockReturnValue({ - timeline: [], - }), + getTimelineOrNull: mockGetTimeline.mockReturnValue(null), + getTimelineTemplateOrNull: mockGetTemplateTimeline.mockReturnValue(null), persistTimeline: mockPersistTimeline, }; }); - jest.doMock('../../pinned_event/saved_object', () => { + jest.doMock('../../../saved_object/pinned_events', () => { return { persistPinnedEventOnTimeline: mockPersistPinnedEventOnTimeline, }; }); - jest.doMock('../../note/saved_object', () => { + jest.doMock('../../../saved_object/notes', () => { return { persistNote: mockPersistNote, }; }); - const updateTimelinesRoute = jest.requireActual('./update_timelines_route') - .updateTimelinesRoute; + const updateTimelinesRoute = jest.requireActual('./index').updateTimelinesRoute; updateTimelinesRoute(server.router, createMockConfig(), securitySetup); }); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/update_timelines_route.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/patch_timelines/index.ts similarity index 74% rename from x-pack/plugins/security_solution/server/lib/timeline/routes/update_timelines_route.ts rename to x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/patch_timelines/index.ts index 9b8a689a092a21..61880789eca849 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/update_timelines_route.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/patch_timelines/index.ts @@ -5,20 +5,20 @@ * 2.0. */ -import type { SecuritySolutionPluginRouter } from '../../../types'; +import type { SecuritySolutionPluginRouter } from '../../../../../types'; -import { TIMELINE_URL } from '../../../../common/constants'; +import { TIMELINE_URL } from '../../../../../../common/constants'; -import { SetupPlugins } from '../../../plugin'; -import { buildRouteValidationWithExcess } from '../../../utils/build_validation/route_validation'; -import { ConfigType } from '../../..'; +import { SetupPlugins } from '../../../../../plugin'; +import { buildRouteValidationWithExcess } from '../../../../../utils/build_validation/route_validation'; +import { ConfigType } from '../../../../..'; -import { transformError, buildSiemResponse } from '../../detection_engine/routes/utils'; +import { transformError, buildSiemResponse } from '../../../../detection_engine/routes/utils'; -import { updateTimelineSchema } from './schemas/update_timelines_schema'; -import { buildFrameworkRequest, TimelineStatusActions } from './utils/common'; -import { createTimelines } from './utils/create_timelines'; -import { CompareTimelinesStatus } from './utils/compare_timelines_status'; +import { patchTimelineSchema } from '../../../schemas/timelines/patch_timelines_schema'; +import { buildFrameworkRequest, TimelineStatusActions } from '../../../utils/common'; +import { createTimelines } from '../create_timelines'; +import { CompareTimelinesStatus } from '../../../utils/compare_timelines_status'; export const updateTimelinesRoute = ( router: SecuritySolutionPluginRouter, @@ -29,7 +29,7 @@ export const updateTimelinesRoute = ( { path: TIMELINE_URL, validate: { - body: buildRouteValidationWithExcess(updateTimelineSchema), + body: buildRouteValidationWithExcess(patchTimelineSchema), }, options: { tags: ['access:securitySolution'], diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/create_timelines.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/create_timelines.ts deleted file mode 100644 index fb88bcd4d79601..00000000000000 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/create_timelines.ts +++ /dev/null @@ -1,206 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { isEmpty } from 'lodash/fp'; - -import moment from 'moment'; -import * as timelineLib from '../../saved_object'; -import * as pinnedEventLib from '../../../pinned_event/saved_object'; -import * as noteLib from '../../../note/saved_object'; -import { FrameworkRequest } from '../../../framework'; -import { SavedTimeline, TimelineSavedObject } from '../../../../../common/types/timeline'; -import { SavedNote } from '../../../../../common/types/timeline/note'; -import { NoteResult, ResponseTimeline } from '../../../../graphql/types'; - -export const saveTimelines = ( - frameworkRequest: FrameworkRequest, - timeline: SavedTimeline, - timelineSavedObjectId?: string | null, - timelineVersion?: string | null, - isImmutable?: boolean -): Promise => { - return timelineLib.persistTimeline( - frameworkRequest, - timelineSavedObjectId ?? null, - timelineVersion ?? null, - timeline, - isImmutable - ); -}; - -export const savePinnedEvents = ( - frameworkRequest: FrameworkRequest, - timelineSavedObjectId: string, - pinnedEventIds: string[] -) => - Promise.all( - pinnedEventIds.map((eventId) => - pinnedEventLib.persistPinnedEventOnTimeline( - frameworkRequest, - null, // pinnedEventSavedObjectId - eventId, - timelineSavedObjectId - ) - ) - ); - -const getNewNote = async ( - frameworkRequest: FrameworkRequest, - note: NoteResult, - timelineSavedObjectId: string, - overrideOwner: boolean -): Promise => { - let savedNote = note; - try { - savedNote = await noteLib.getNote(frameworkRequest, note.noteId); - // eslint-disable-next-line no-empty - } catch (e) {} - return overrideOwner - ? { - eventId: note.eventId, - note: note.note, - timelineId: timelineSavedObjectId, - } - : { - eventId: savedNote.eventId, - note: savedNote.note, - created: savedNote.created, - createdBy: savedNote.createdBy, - updated: savedNote.updated, - updatedBy: savedNote.updatedBy, - timelineId: timelineSavedObjectId, - }; -}; - -export const saveNotes = async ( - frameworkRequest: FrameworkRequest, - timelineSavedObjectId: string, - timelineVersion?: string | null, - existingNoteIds?: string[], - newNotes?: NoteResult[], - overrideOwner: boolean = true -) => { - return Promise.all( - newNotes?.map(async (note) => { - const newNote = await getNewNote( - frameworkRequest, - note, - timelineSavedObjectId, - overrideOwner - ); - return noteLib.persistNote( - frameworkRequest, - overrideOwner ? existingNoteIds?.find((nId) => nId === note.noteId) ?? null : null, - timelineVersion ?? null, - newNote, - overrideOwner - ); - }) ?? [] - ); -}; - -interface CreateTimelineProps { - frameworkRequest: FrameworkRequest; - timeline: SavedTimeline; - timelineSavedObjectId?: string | null; - timelineVersion?: string | null; - overrideNotesOwner?: boolean; - pinnedEventIds?: string[] | null; - notes?: NoteResult[]; - existingNoteIds?: string[]; - isImmutable?: boolean; -} - -/** allow overrideNotesOwner means overriding by current username, - * disallow overrideNotesOwner means keep the original username. - * overrideNotesOwner = false only happens when import timeline templates, - * as we want to keep the original creator for notes - **/ -export const createTimelines = async ({ - frameworkRequest, - timeline, - timelineSavedObjectId = null, - timelineVersion = null, - pinnedEventIds = null, - notes = [], - existingNoteIds = [], - isImmutable, - overrideNotesOwner = true, -}: CreateTimelineProps): Promise => { - const timerangeStart = isImmutable - ? moment().subtract(24, 'hours').toISOString() - : timeline.dateRange?.start; - const timerangeEnd = isImmutable ? moment().toISOString() : timeline.dateRange?.end; - const responseTimeline = await saveTimelines( - frameworkRequest, - { ...timeline, dateRange: { start: timerangeStart, end: timerangeEnd } }, - timelineSavedObjectId, - timelineVersion, - isImmutable - ); - const newTimelineSavedObjectId = responseTimeline.timeline.savedObjectId; - const newTimelineVersion = responseTimeline.timeline.version; - - let myPromises: unknown[] = []; - if (pinnedEventIds != null && !isEmpty(pinnedEventIds)) { - myPromises = [ - ...myPromises, - savePinnedEvents( - frameworkRequest, - timelineSavedObjectId ?? newTimelineSavedObjectId, - pinnedEventIds - ), - ]; - } - if (!isEmpty(notes)) { - myPromises = [ - ...myPromises, - saveNotes( - frameworkRequest, - timelineSavedObjectId ?? newTimelineSavedObjectId, - newTimelineVersion, - existingNoteIds, - notes, - overrideNotesOwner - ), - ]; - } - - if (myPromises.length > 0) { - await Promise.all(myPromises); - } - - return responseTimeline; -}; - -export const getTimeline = async ( - frameworkRequest: FrameworkRequest, - savedObjectId: string -): Promise => { - let timeline = null; - try { - timeline = await timelineLib.getTimeline(frameworkRequest, savedObjectId); - // eslint-disable-next-line no-empty - } catch (e) {} - return timeline; -}; - -export const getTemplateTimeline = async ( - frameworkRequest: FrameworkRequest, - templateTimelineId: string -): Promise => { - let templateTimeline = null; - try { - templateTimeline = await timelineLib.getTimelineByTemplateTimelineId( - frameworkRequest, - templateTimelineId - ); - } catch (e) { - return null; - } - return templateTimeline?.timeline[0] ?? null; -}; diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/get_timelines_to_install.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/get_timelines_to_install.ts deleted file mode 100644 index 8ba2174179b16f..00000000000000 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/get_timelines_to_install.ts +++ /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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { ImportTimelinesSchema } from '../schemas/import_timelines_schema'; -import { TimelineSavedObject } from '../../../../../common/types/timeline'; - -export const getTimelinesToInstall = ( - timelinesFromFileSystem: ImportTimelinesSchema[], - installedTimelines: TimelineSavedObject[] -): ImportTimelinesSchema[] => { - return timelinesFromFileSystem.filter( - (timeline) => - !installedTimelines.some( - (installedTimeline) => installedTimeline.templateTimelineId === timeline.templateTimelineId - ) - ); -}; diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/get_timelines_to_update.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/get_timelines_to_update.ts deleted file mode 100644 index 6bdc61c1fa595c..00000000000000 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/get_timelines_to_update.ts +++ /dev/null @@ -1,23 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { ImportTimelinesSchema } from '../schemas/import_timelines_schema'; -import { TimelineSavedObject } from '../../../../../common/types/timeline'; - -export const getTimelinesToUpdate = ( - timelinesFromFileSystem: ImportTimelinesSchema[], - installedTimelines: TimelineSavedObject[] -): ImportTimelinesSchema[] => { - return timelinesFromFileSystem.filter((timeline) => - installedTimelines.some((installedTimeline) => { - return ( - timeline.templateTimelineId === installedTimeline.templateTimelineId && - timeline.templateTimelineVersion! > installedTimeline.templateTimelineVersion! - ); - }) - ); -}; diff --git a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/index.ts new file mode 100644 index 00000000000000..2b5c3b19ee74aa --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * as timeline from './timelines'; +export * as note from './notes'; +export * as pinnedEvent from './pinned_events'; diff --git a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/notes/get_overridable_note.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/notes/get_overridable_note.ts new file mode 100644 index 00000000000000..1ff1c37a163576 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/notes/get_overridable_note.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { SavedNote } from '../../../../../common/types/timeline/note'; +import { NoteResult } from '../../../../graphql/types'; +import { FrameworkRequest } from '../../../framework'; +import { getNote } from './saved_object'; + +export const getOverridableNote = async ( + frameworkRequest: FrameworkRequest, + note: NoteResult, + timelineSavedObjectId: string, + overrideOwner: boolean +): Promise => { + let savedNote = note; + try { + savedNote = await getNote(frameworkRequest, note.noteId); + // eslint-disable-next-line no-empty + } catch (e) {} + return overrideOwner + ? { + eventId: note.eventId, + note: note.note, + timelineId: timelineSavedObjectId, + } + : { + eventId: savedNote.eventId, + note: savedNote.note, + created: savedNote.created, + createdBy: savedNote.createdBy, + updated: savedNote.updated, + updatedBy: savedNote.updatedBy, + timelineId: timelineSavedObjectId, + }; +}; diff --git a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/notes/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/notes/index.ts new file mode 100644 index 00000000000000..9addf0f80e1242 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/notes/index.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FrameworkRequest } from '../../../framework'; +import { PageInfoNote, ResponseNote, ResponseNotes, SortNote } from '../../../../graphql/types'; +import { SavedNote, NoteSavedObject } from '../../../../../common/types/timeline/note'; + +export * from './saved_object'; +export interface Notes { + deleteNote: (request: FrameworkRequest, noteIds: string[]) => Promise; + deleteNoteByTimelineId: (request: FrameworkRequest, noteIds: string) => Promise; + getNote: (request: FrameworkRequest, noteId: string) => Promise; + getNotesByEventId: (request: FrameworkRequest, noteId: string) => Promise; + getNotesByTimelineId: (request: FrameworkRequest, noteId: string) => Promise; + getAllNotes: ( + request: FrameworkRequest, + pageInfo: PageInfoNote | null, + search: string | null, + sort: SortNote | null + ) => Promise; + persistNote: ( + request: FrameworkRequest, + noteId: string | null, + version: string | null, + note: SavedNote, + overrideOwner: boolean + ) => Promise; + convertSavedObjectToSavedNote: ( + savedObject: unknown, + timelineVersion?: string | undefined | null + ) => NoteSavedObject; +} diff --git a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/notes/persist_notes.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/notes/persist_notes.ts new file mode 100644 index 00000000000000..7f6a355f43df00 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/notes/persist_notes.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FrameworkRequest } from '../../../framework'; +import { NoteResult } from '../../../../graphql/types'; +import { persistNote } from './saved_object'; +import { getOverridableNote } from './get_overridable_note'; + +export const persistNotes = async ( + frameworkRequest: FrameworkRequest, + timelineSavedObjectId: string, + timelineVersion?: string | null, + existingNoteIds?: string[], + newNotes?: NoteResult[], + overrideOwner: boolean = true +) => { + return Promise.all( + newNotes?.map(async (note) => { + const newNote = await getOverridableNote( + frameworkRequest, + note, + timelineSavedObjectId, + overrideOwner + ); + return persistNote( + frameworkRequest, + overrideOwner ? existingNoteIds?.find((nId) => nId === note.noteId) ?? null : null, + timelineVersion ?? null, + newNote, + overrideOwner + ); + }) ?? [] + ); +}; diff --git a/x-pack/plugins/security_solution/server/lib/note/saved_object.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/notes/saved_object.ts similarity index 81% rename from x-pack/plugins/security_solution/server/lib/note/saved_object.ts rename to x-pack/plugins/security_solution/server/lib/timeline/saved_object/notes/saved_object.ts index 79c50aba7141c6..8016fdf12881d5 100644 --- a/x-pack/plugins/security_solution/server/lib/note/saved_object.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/notes/saved_object.ts @@ -13,51 +13,25 @@ import { pipe } from 'fp-ts/lib/pipeable'; import { map, fold } from 'fp-ts/lib/Either'; import { identity } from 'fp-ts/lib/function'; -import { SavedObjectsFindOptions } from '../../../../../../src/core/server'; -import { AuthenticatedUser } from '../../../../security/common/model'; -import { UNAUTHENTICATED_USER } from '../../../common/constants'; +import { SavedObjectsFindOptions } from '../../../../../../../../src/core/server'; +import { AuthenticatedUser } from '../../../../../../security/common/model'; +import { UNAUTHENTICATED_USER } from '../../../../../common/constants'; import { SavedNote, NoteSavedObjectRuntimeType, NoteSavedObject, -} from '../../../common/types/timeline/note'; +} from '../../../../../common/types/timeline/note'; import { PageInfoNote, ResponseNote, ResponseNotes, SortNote, NoteResult, -} from '../../graphql/types'; -import { FrameworkRequest } from '../framework'; -import { noteSavedObjectType } from './saved_object_mappings'; -import { pickSavedTimeline } from '../timeline/pick_saved_timeline'; -import { convertSavedObjectToSavedTimeline } from '../timeline/convert_saved_object_to_savedtimeline'; -import { timelineSavedObjectType } from '../timeline/saved_object_mappings'; - -export interface Note { - deleteNote: (request: FrameworkRequest, noteIds: string[]) => Promise; - deleteNoteByTimelineId: (request: FrameworkRequest, noteIds: string) => Promise; - getNote: (request: FrameworkRequest, noteId: string) => Promise; - getNotesByEventId: (request: FrameworkRequest, noteId: string) => Promise; - getNotesByTimelineId: (request: FrameworkRequest, noteId: string) => Promise; - getAllNotes: ( - request: FrameworkRequest, - pageInfo: PageInfoNote | null, - search: string | null, - sort: SortNote | null - ) => Promise; - persistNote: ( - request: FrameworkRequest, - noteId: string | null, - version: string | null, - note: SavedNote, - overrideOwner: boolean - ) => Promise; - convertSavedObjectToSavedNote: ( - savedObject: unknown, - timelineVersion?: string | undefined | null - ) => NoteSavedObject; -} +} from '../../../../graphql/types'; +import { FrameworkRequest } from '../../../framework'; +import { noteSavedObjectType } from '../../saved_object_mappings/notes'; +import { convertSavedObjectToSavedTimeline, pickSavedTimeline } from '../timelines'; +import { timelineSavedObjectType } from '../../saved_object_mappings'; export const deleteNote = async (request: FrameworkRequest, noteIds: string[]) => { const savedObjectsClient = request.context.core.savedObjects.client; diff --git a/x-pack/plugins/security_solution/server/lib/pinned_event/saved_object.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/pinned_events/index.ts similarity index 88% rename from x-pack/plugins/security_solution/server/lib/pinned_event/saved_object.ts rename to x-pack/plugins/security_solution/server/lib/timeline/saved_object/pinned_events/index.ts index d59f31504278bf..6467d1d43d807b 100644 --- a/x-pack/plugins/security_solution/server/lib/pinned_event/saved_object.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/pinned_events/index.ts @@ -11,21 +11,25 @@ import { pipe } from 'fp-ts/lib/pipeable'; import { map, fold } from 'fp-ts/lib/Either'; import { identity } from 'fp-ts/lib/function'; -import { SavedObjectsFindOptions } from '../../../../../../src/core/server'; -import { AuthenticatedUser } from '../../../../security/common/model'; -import { UNAUTHENTICATED_USER } from '../../../common/constants'; +import { SavedObjectsFindOptions } from '../../../../../../../../src/core/server'; +import { AuthenticatedUser } from '../../../../../../security/common/model'; +import { UNAUTHENTICATED_USER } from '../../../../../common/constants'; import { PinnedEventSavedObject, PinnedEventSavedObjectRuntimeType, SavedPinnedEvent, -} from '../../../common/types/timeline/pinned_event'; -import { FrameworkRequest } from '../framework'; +} from '../../../../../common/types/timeline/pinned_event'; +import { FrameworkRequest } from '../../../framework'; -import { PageInfoNote, SortNote, PinnedEvent as PinnedEventResponse } from '../../graphql/types'; -import { pickSavedTimeline } from '../timeline/pick_saved_timeline'; -import { convertSavedObjectToSavedTimeline } from '../timeline/convert_saved_object_to_savedtimeline'; -import { pinnedEventSavedObjectType } from './saved_object_mappings'; -import { timelineSavedObjectType } from '../timeline/saved_object_mappings'; +import { + PageInfoNote, + SortNote, + PinnedEvent as PinnedEventResponse, +} from '../../../../graphql/types'; +import { pickSavedTimeline } from '../../saved_object/timelines'; +import { convertSavedObjectToSavedTimeline } from '../timelines'; +import { pinnedEventSavedObjectType } from '../../saved_object_mappings/pinned_events'; +import { timelineSavedObjectType } from '../../saved_object_mappings'; export interface PinnedEvent { deletePinnedEventOnTimeline: ( @@ -233,6 +237,22 @@ const getAllSavedPinnedEvents = async ( ); }; +export const savePinnedEvents = ( + frameworkRequest: FrameworkRequest, + timelineSavedObjectId: string, + pinnedEventIds: string[] +) => + Promise.all( + pinnedEventIds.map((eventId) => + persistPinnedEventOnTimeline( + frameworkRequest, + null, // pinnedEventSavedObjectId + eventId, + timelineSavedObjectId + ) + ) + ); + export const convertSavedObjectToSavedPinnedEvent = ( savedObject: unknown, timelineVersion?: string | undefined | null diff --git a/x-pack/plugins/security_solution/server/lib/timeline/convert_saved_object_to_savedtimeline.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/convert_saved_object_to_savedtimeline.ts similarity index 98% rename from x-pack/plugins/security_solution/server/lib/timeline/convert_saved_object_to_savedtimeline.ts rename to x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/convert_saved_object_to_savedtimeline.ts index b28846ca3c03c9..27826a92b6d964 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/convert_saved_object_to_savedtimeline.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/convert_saved_object_to_savedtimeline.ts @@ -16,7 +16,7 @@ import { TimelineSavedObject, TimelineType, TimelineStatus, -} from '../../../common/types/timeline'; +} from '../../../../../common/types/timeline'; // TODO: Added to support legacy TimelineType.draft, can be removed in 7.10 export const TimelineSavedObjectWithDraftRuntimeType = intersection([ diff --git a/x-pack/plugins/security_solution/server/lib/timeline/saved_object.test.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/index.test.ts similarity index 95% rename from x-pack/plugins/security_solution/server/lib/timeline/saved_object.test.ts rename to x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/index.test.ts index d329fa31a3f04f..61e25d419a0e45 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/saved_object.test.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/index.test.ts @@ -5,28 +5,28 @@ * 2.0. */ -import { FrameworkRequest } from '../framework'; -import { mockGetTimelineValue, mockSavedObject } from './routes/__mocks__/import_timelines'; +import { FrameworkRequest } from '../../../framework'; +import { mockGetTimelineValue, mockSavedObject } from '../../__mocks__/import_timelines'; import { convertStringToBase64, getExistingPrepackagedTimelines, getAllTimeline, AllTimelinesResponse, -} from './saved_object'; +} from '.'; import { convertSavedObjectToSavedTimeline } from './convert_saved_object_to_savedtimeline'; -import { getNotesByTimelineId } from '../note/saved_object'; -import { getAllPinnedEventsByTimelineId } from '../pinned_event/saved_object'; +import { getNotesByTimelineId } from '../notes/saved_object'; +import { getAllPinnedEventsByTimelineId } from '../pinned_events'; jest.mock('./convert_saved_object_to_savedtimeline', () => ({ convertSavedObjectToSavedTimeline: jest.fn(), })); -jest.mock('../note/saved_object', () => ({ +jest.mock('../notes/saved_object', () => ({ getNotesByTimelineId: jest.fn().mockResolvedValue([]), })); -jest.mock('../pinned_event/saved_object', () => ({ +jest.mock('../pinned_events', () => ({ getAllPinnedEventsByTimelineId: jest.fn().mockResolvedValue([]), })); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/saved_object.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/index.ts similarity index 92% rename from x-pack/plugins/security_solution/server/lib/timeline/saved_object.ts rename to x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/index.ts index 6d09ef683dd491..0f624ef5420bcf 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/saved_object.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/index.ts @@ -7,17 +7,17 @@ import { getOr } from 'lodash/fp'; -import { SavedObjectsFindOptions } from '../../../../../../src/core/server'; -import { UNAUTHENTICATED_USER } from '../../../common/constants'; -import { NoteSavedObject } from '../../../common/types/timeline/note'; -import { PinnedEventSavedObject } from '../../../common/types/timeline/pinned_event'; +import { SavedObjectsFindOptions } from '../../../../../../../../src/core/server'; +import { UNAUTHENTICATED_USER } from '../../../../../common/constants'; +import { NoteSavedObject } from '../../../../../common/types/timeline/note'; +import { PinnedEventSavedObject } from '../../../../../common/types/timeline/pinned_event'; import { SavedTimeline, TimelineSavedObject, TimelineTypeLiteralWithNull, ExportTimelineNotFoundError, TimelineStatusLiteralWithNull, -} from '../../../common/types/timeline'; +} from '../../../../../common/types/timeline'; import { ResponseTimeline, PageInfoTimeline, @@ -27,15 +27,17 @@ import { TimelineType, TimelineStatus, Maybe, -} from '../../graphql/types'; -import { FrameworkRequest } from '../framework'; -import * as note from '../note/saved_object'; -import * as pinnedEvent from '../pinned_event/saved_object'; +} from '../../../../graphql/types'; +import { FrameworkRequest } from '../../../framework'; +import * as note from '../notes/saved_object'; +import * as pinnedEvent from '../pinned_events'; import { convertSavedObjectToSavedTimeline } from './convert_saved_object_to_savedtimeline'; import { pickSavedTimeline } from './pick_saved_timeline'; -import { timelineSavedObjectType } from './saved_object_mappings'; -import { draftTimelineDefaults } from './default_timeline'; -import { AuthenticatedUser } from '../../../../security/server'; +import { timelineSavedObjectType } from '../../saved_object_mappings/'; +import { draftTimelineDefaults } from '../../utils/default_timeline'; +import { AuthenticatedUser } from '../../../../../../security/server'; +export { pickSavedTimeline } from './pick_saved_timeline'; +export { convertSavedObjectToSavedTimeline } from './convert_saved_object_to_savedtimeline'; interface ResponseTimelines { timeline: TimelineSavedObject[]; @@ -126,6 +128,18 @@ export const getTimeline = async ( return getSavedTimeline(request, timelineIdToUse); }; +export const getTimelineOrNull = async ( + frameworkRequest: FrameworkRequest, + savedObjectId: string +): Promise => { + let timeline = null; + try { + timeline = await getTimeline(frameworkRequest, savedObjectId); + // eslint-disable-next-line no-empty + } catch (e) {} + return timeline; +}; + export const getTimelineByTemplateTimelineId = async ( request: FrameworkRequest, templateTimelineId: string @@ -140,6 +154,19 @@ export const getTimelineByTemplateTimelineId = async ( return getAllSavedTimeline(request, options); }; +export const getTimelineTemplateOrNull = async ( + frameworkRequest: FrameworkRequest, + templateTimelineId: string +): Promise => { + let templateTimeline = null; + try { + templateTimeline = await getTimelineByTemplateTimelineId(frameworkRequest, templateTimelineId); + } catch (e) { + return null; + } + return templateTimeline?.timeline[0] ?? null; +}; + /** The filter here is able to handle the legacy data, * which has no timelineType exists in the savedObject */ const getTimelineTypeFilter = ( diff --git a/x-pack/plugins/security_solution/server/lib/timeline/pick_saved_timeline.test.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/pick_saved_timeline.test.ts similarity index 98% rename from x-pack/plugins/security_solution/server/lib/timeline/pick_saved_timeline.test.ts rename to x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/pick_saved_timeline.test.ts index 26f6d0eae4f93a..94e70e4eb001bb 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/pick_saved_timeline.test.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/pick_saved_timeline.test.ts @@ -5,9 +5,9 @@ * 2.0. */ -import { AuthenticatedUser } from '../../../../security/common/model'; +import { AuthenticatedUser } from '../../../../../../security/common/model'; -import { TimelineStatus, TimelineType } from '../../../common/types/timeline'; +import { TimelineStatus, TimelineType } from '../../../../../common/types/timeline'; import { pickSavedTimeline } from './pick_saved_timeline'; diff --git a/x-pack/plugins/security_solution/server/lib/timeline/pick_saved_timeline.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/pick_saved_timeline.ts similarity index 90% rename from x-pack/plugins/security_solution/server/lib/timeline/pick_saved_timeline.ts rename to x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/pick_saved_timeline.ts index 5b6752c57ecb17..a28084cd78154e 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/pick_saved_timeline.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/pick_saved_timeline.ts @@ -6,9 +6,9 @@ */ import { isEmpty } from 'lodash/fp'; -import { AuthenticatedUser } from '../../../../security/common/model'; -import { UNAUTHENTICATED_USER } from '../../../common/constants'; -import { SavedTimeline, TimelineType, TimelineStatus } from '../../../common/types/timeline'; +import { AuthenticatedUser } from '../../../../../../security/common/model'; +import { UNAUTHENTICATED_USER } from '../../../../../common/constants'; +import { SavedTimeline, TimelineType, TimelineStatus } from '../../../../../common/types/timeline'; export const pickSavedTimeline = ( timelineId: string | null, diff --git a/x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/index.ts new file mode 100644 index 00000000000000..2d3e39685090fa --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './notes'; +export * from './pinned_events'; +export * from './timelines'; diff --git a/x-pack/plugins/security_solution/server/lib/note/saved_object_mappings.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/notes.ts similarity index 87% rename from x-pack/plugins/security_solution/server/lib/note/saved_object_mappings.ts rename to x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/notes.ts index 02cb4ee2457cb8..5815747d3e7202 100644 --- a/x-pack/plugins/security_solution/server/lib/note/saved_object_mappings.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/notes.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { SavedObjectsType } from '../../../../../../src/core/server'; +import { SavedObjectsType } from '../../../../../../../src/core/server'; export const noteSavedObjectType = 'siem-ui-timeline-note'; @@ -35,7 +35,7 @@ export const noteSavedObjectMappings: SavedObjectsType['mappings'] = { }, }; -export const type: SavedObjectsType = { +export const noteType: SavedObjectsType = { name: noteSavedObjectType, hidden: false, namespaceType: 'single', diff --git a/x-pack/plugins/security_solution/server/lib/pinned_event/saved_object_mappings.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/pinned_events.ts similarity index 86% rename from x-pack/plugins/security_solution/server/lib/pinned_event/saved_object_mappings.ts rename to x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/pinned_events.ts index f8163c20ec8034..fbbffe35a58c0e 100644 --- a/x-pack/plugins/security_solution/server/lib/pinned_event/saved_object_mappings.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/pinned_events.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { SavedObjectsType } from '../../../../../../src/core/server'; +import { SavedObjectsType } from '../../../../../../../src/core/server'; export const pinnedEventSavedObjectType = 'siem-ui-timeline-pinned-event'; @@ -32,7 +32,7 @@ export const pinnedEventSavedObjectMappings: SavedObjectsType['mappings'] = { }, }; -export const type: SavedObjectsType = { +export const pinnedEventType: SavedObjectsType = { name: pinnedEventSavedObjectType, hidden: false, namespaceType: 'single', diff --git a/x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/timelines.ts similarity index 98% rename from x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings.ts rename to x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/timelines.ts index 8e0d88a9b9a6ff..a2de616e68c797 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/timelines.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { SavedObjectsType } from '../../../../../../src/core/server'; +import { SavedObjectsType } from '../../../../../../../src/core/server'; export const timelineSavedObjectType = 'siem-ui-timeline'; @@ -317,7 +317,7 @@ export const timelineSavedObjectMappings: SavedObjectsType['mappings'] = { }, }; -export const type: SavedObjectsType = { +export const timelineType: SavedObjectsType = { name: timelineSavedObjectType, hidden: false, namespaceType: 'single', diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/schemas/clean_draft_timelines_schema.ts b/x-pack/plugins/security_solution/server/lib/timeline/schemas/draft_timelines/clean_draft_timelines_schema.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/timeline/routes/schemas/clean_draft_timelines_schema.ts rename to x-pack/plugins/security_solution/server/lib/timeline/schemas/draft_timelines/clean_draft_timelines_schema.ts diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/schemas/get_draft_timelines_schema.ts b/x-pack/plugins/security_solution/server/lib/timeline/schemas/draft_timelines/get_draft_timelines_schema.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/timeline/routes/schemas/get_draft_timelines_schema.ts rename to x-pack/plugins/security_solution/server/lib/timeline/schemas/draft_timelines/get_draft_timelines_schema.ts diff --git a/x-pack/plugins/security_solution/server/lib/timeline/schemas/draft_timelines/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/schemas/draft_timelines/index.ts new file mode 100644 index 00000000000000..d98958dee45101 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/timeline/schemas/draft_timelines/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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { cleanDraftTimelineSchema } from './clean_draft_timelines_schema'; +export { getDraftTimelineSchema } from './get_draft_timelines_schema'; diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/schemas/schemas.ts b/x-pack/plugins/security_solution/server/lib/timeline/schemas/notes/index.ts similarity index 87% rename from x-pack/plugins/security_solution/server/lib/timeline/routes/schemas/schemas.ts rename to x-pack/plugins/security_solution/server/lib/timeline/schemas/notes/index.ts index c5951787adcd58..de1e3578963537 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/schemas/schemas.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/schemas/notes/index.ts @@ -11,4 +11,3 @@ import { SavedNoteRuntimeType } from '../../../../../common/types/timeline/note' export const eventNotes = unionWithNullType(runtimeTypes.array(SavedNoteRuntimeType)); export const globalNotes = unionWithNullType(runtimeTypes.array(SavedNoteRuntimeType)); -export const pinnedEventIds = unionWithNullType(runtimeTypes.array(runtimeTypes.string)); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/schemas/pinned_events/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/schemas/pinned_events/index.ts new file mode 100644 index 00000000000000..29afda10dce800 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/timeline/schemas/pinned_events/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import * as runtimeTypes from 'io-ts'; +import { unionWithNullType } from '../../../../../common/utility_types'; + +export const pinnedEventIds = unionWithNullType(runtimeTypes.array(runtimeTypes.string)); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/schemas/create_timelines_schema.ts b/x-pack/plugins/security_solution/server/lib/timeline/schemas/timelines/create_timelines_schema.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/timeline/routes/schemas/create_timelines_schema.ts rename to x-pack/plugins/security_solution/server/lib/timeline/schemas/timelines/create_timelines_schema.ts diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/schemas/export_timelines_schema.ts b/x-pack/plugins/security_solution/server/lib/timeline/schemas/timelines/export_timelines_schema.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/timeline/routes/schemas/export_timelines_schema.ts rename to x-pack/plugins/security_solution/server/lib/timeline/schemas/timelines/export_timelines_schema.ts diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/schemas/get_timeline_by_id_schema.ts b/x-pack/plugins/security_solution/server/lib/timeline/schemas/timelines/get_timeline_by_id_schema.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/timeline/routes/schemas/get_timeline_by_id_schema.ts rename to x-pack/plugins/security_solution/server/lib/timeline/schemas/timelines/get_timeline_by_id_schema.ts diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/schemas/import_timelines_schema.ts b/x-pack/plugins/security_solution/server/lib/timeline/schemas/timelines/import_timelines_schema.ts similarity index 93% rename from x-pack/plugins/security_solution/server/lib/timeline/routes/schemas/import_timelines_schema.ts rename to x-pack/plugins/security_solution/server/lib/timeline/schemas/timelines/import_timelines_schema.ts index 18a9fba7805498..c1742a3ac1d85b 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/schemas/import_timelines_schema.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/schemas/timelines/import_timelines_schema.ts @@ -8,10 +8,11 @@ import * as rt from 'io-ts'; import { SavedTimelineRuntimeType } from '../../../../../common/types/timeline'; - -import { eventNotes, globalNotes, pinnedEventIds } from './schemas'; import { unionWithNullType } from '../../../../../common/utility_types'; +import { eventNotes, globalNotes } from '../notes'; +import { pinnedEventIds } from '../pinned_events'; + export const ImportTimelinesSchemaRt = rt.intersection([ SavedTimelineRuntimeType, rt.type({ diff --git a/x-pack/plugins/security_solution/server/lib/timeline/schemas/timelines/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/schemas/timelines/index.ts new file mode 100644 index 00000000000000..e85ae2ab4ae865 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/timeline/schemas/timelines/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +export * from './create_timelines_schema'; +export * from './export_timelines_schema'; +export * from './get_timeline_by_id_schema'; +export * from './patch_timelines_schema'; diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/schemas/update_timelines_schema.ts b/x-pack/plugins/security_solution/server/lib/timeline/schemas/timelines/patch_timelines_schema.ts similarity index 92% rename from x-pack/plugins/security_solution/server/lib/timeline/routes/schemas/update_timelines_schema.ts rename to x-pack/plugins/security_solution/server/lib/timeline/schemas/timelines/patch_timelines_schema.ts index 7d3679e3ad9106..bb45be082f5f93 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/schemas/update_timelines_schema.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/schemas/timelines/patch_timelines_schema.ts @@ -10,7 +10,7 @@ import * as rt from 'io-ts'; import { SavedTimelineRuntimeType } from '../../../../../common/types/timeline'; import { unionWithNullType } from '../../../../../common/utility_types'; -export const updateTimelineSchema = rt.type({ +export const patchTimelineSchema = rt.type({ timeline: SavedTimelineRuntimeType, timelineId: unionWithNullType(rt.string), version: unionWithNullType(rt.string), diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/check_timelines_status.ts b/x-pack/plugins/security_solution/server/lib/timeline/utils/check_timelines_status.ts similarity index 51% rename from x-pack/plugins/security_solution/server/lib/timeline/routes/utils/check_timelines_status.ts rename to x-pack/plugins/security_solution/server/lib/timeline/utils/check_timelines_status.ts index f03201d07a45cd..560df1112ac58e 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/check_timelines_status.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/utils/check_timelines_status.ts @@ -6,18 +6,57 @@ */ import path, { join, resolve } from 'path'; +import * as rt from 'io-ts'; +import { + TimelineSavedToReturnObjectRuntimeType, + TimelineSavedObject, +} from '../../../../common/types/timeline'; -import { TimelineSavedObject } from '../../../../../common/types/timeline'; +import { + ImportTimelinesSchema, + ImportTimelinesSchemaRt, +} from '../schemas/timelines/import_timelines_schema'; +import { unionWithNullType } from '../../../../common/utility_types'; -import { FrameworkRequest } from '../../../framework'; +import { FrameworkRequest } from '../../framework'; -import { getExistingPrepackagedTimelines } from '../../saved_object'; - -import { CheckTimelineStatusRt } from '../schemas/check_timelines_status_schema'; +import { getExistingPrepackagedTimelines } from '../saved_object/timelines'; import { loadData, getReadables } from './common'; -import { getTimelinesToInstall } from './get_timelines_to_install'; -import { getTimelinesToUpdate } from './get_timelines_to_update'; + +export const checkTimelineStatusRt = rt.type({ + timelinesToInstall: rt.array(unionWithNullType(ImportTimelinesSchemaRt)), + timelinesToUpdate: rt.array(unionWithNullType(ImportTimelinesSchemaRt)), + prepackagedTimelines: rt.array(unionWithNullType(TimelineSavedToReturnObjectRuntimeType)), +}); + +export type CheckTimelineStatusRt = rt.TypeOf; + +export const getTimelinesToUpdate = ( + timelinesFromFileSystem: ImportTimelinesSchema[], + installedTimelines: TimelineSavedObject[] +): ImportTimelinesSchema[] => { + return timelinesFromFileSystem.filter((timeline) => + installedTimelines.some((installedTimeline) => { + return ( + timeline.templateTimelineId === installedTimeline.templateTimelineId && + timeline.templateTimelineVersion! > installedTimeline.templateTimelineVersion! + ); + }) + ); +}; + +export const getTimelinesToInstall = ( + timelinesFromFileSystem: ImportTimelinesSchema[], + installedTimelines: TimelineSavedObject[] +): ImportTimelinesSchema[] => { + return timelinesFromFileSystem.filter( + (timeline) => + !installedTimelines.some( + (installedTimeline) => installedTimeline.templateTimelineId === timeline.templateTimelineId + ) + ); +}; export const checkTimelinesStatus = async ( frameworkRequest: FrameworkRequest, @@ -30,7 +69,7 @@ export const checkTimelinesStatus = async ( timeline: TimelineSavedObject[]; }; const dir = resolve( - join(__dirname, filePath ?? '../../../detection_engine/rules/prepackaged_timelines') + join(__dirname, filePath ?? '../../detection_engine/rules/prepackaged_timelines') ); const file = fileName ?? 'index.ndjson'; const dataPath = path.join(dir, file); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/common.ts b/x-pack/plugins/security_solution/server/lib/timeline/utils/common.ts similarity index 97% rename from x-pack/plugins/security_solution/server/lib/timeline/routes/utils/common.ts rename to x-pack/plugins/security_solution/server/lib/timeline/utils/common.ts index 6cad35ea2e2d77..443742ae88f0de 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/common.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/utils/common.ts @@ -12,10 +12,10 @@ import { Readable } from 'stream'; import { createListStream } from '@kbn/utils'; import { KibanaRequest } from 'src/core/server'; -import { SetupPlugins } from '../../../../plugin'; -import type { SecuritySolutionRequestHandlerContext } from '../../../../types'; +import { SetupPlugins } from '../../../plugin'; +import type { SecuritySolutionRequestHandlerContext } from '../../../types'; -import { FrameworkRequest } from '../../../framework'; +import { FrameworkRequest } from '../../framework'; export const buildFrameworkRequest = async ( context: SecuritySolutionRequestHandlerContext, diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/compare_timelines_status.test.ts b/x-pack/plugins/security_solution/server/lib/timeline/utils/compare_timelines_status.test.ts similarity index 90% rename from x-pack/plugins/security_solution/server/lib/timeline/routes/utils/compare_timelines_status.test.ts rename to x-pack/plugins/security_solution/server/lib/timeline/utils/compare_timelines_status.test.ts index 2117fd60cdbe13..7fb49a3923a253 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/compare_timelines_status.test.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/utils/compare_timelines_status.test.ts @@ -5,8 +5,8 @@ * 2.0. */ -import { TimelineType, TimelineStatus } from '../../../../../common/types/timeline'; -import { FrameworkRequest } from '../../../framework'; +import { TimelineType, TimelineStatus } from '../../../../common/types/timeline'; +import { FrameworkRequest } from '../../framework'; import { mockUniqueParsedObjects, @@ -46,12 +46,10 @@ describe('CompareTimelinesStatus', () => { }); beforeEach(async () => { - jest.doMock('../../saved_object', () => { + jest.doMock('../saved_object/timelines', () => { return { - getTimeline: mockGetTimeline.mockReturnValue(mockGetTimelineValue), - getTimelineByTemplateTimelineId: mockGetTemplateTimeline.mockReturnValue({ - timeline: [], - }), + getTimelineOrNull: mockGetTimeline.mockReturnValue(mockGetTimelineValue), + getTimelineTemplateOrNull: mockGetTemplateTimeline.mockReturnValue(null), }; }); @@ -122,12 +120,10 @@ describe('CompareTimelinesStatus', () => { }); beforeEach(async () => { - jest.doMock('../../saved_object', () => { + jest.doMock('../saved_object/timelines', () => { return { - getTimeline: mockGetTimeline.mockReturnValue(null), - getTimelineByTemplateTimelineId: mockGetTemplateTimeline.mockReturnValue({ - timeline: [], - }), + getTimelineOrNull: mockGetTimeline.mockReturnValue(null), + getTimelineTemplateOrNull: mockGetTemplateTimeline.mockReturnValue(null), }; }); @@ -189,11 +185,11 @@ describe('CompareTimelinesStatus', () => { let timelineObj: TimelinesStatusType; beforeEach(async () => { - jest.doMock('../../saved_object', () => ({ - getTimeline: mockGetTimeline.mockReturnValue(mockGetTemplateTimelineValue), - getTimelineByTemplateTimelineId: mockGetTemplateTimeline.mockReturnValue({ - timeline: [mockGetTemplateTimelineValue], - }), + jest.doMock('../saved_object/timelines', () => ({ + getTimelineOrNull: mockGetTimeline.mockReturnValue(mockGetTemplateTimelineValue), + getTimelineTemplateOrNull: mockGetTemplateTimeline.mockReturnValue( + mockGetTemplateTimelineValue + ), })); const CompareTimelinesStatus = jest.requireActual('./compare_timelines_status') @@ -275,9 +271,9 @@ describe('CompareTimelinesStatus', () => { }); beforeEach(async () => { - jest.doMock('../../saved_object', () => ({ - getTimeline: mockGetTimeline, - getTimelineByTemplateTimelineId: mockGetTemplateTimeline, + jest.doMock('../saved_object/timelines', () => ({ + getTimelineOrNull: mockGetTimeline, + getTimelineTemplateOrNull: mockGetTemplateTimeline, })); const CompareTimelinesStatus = jest.requireActual('./compare_timelines_status') @@ -366,12 +362,10 @@ describe('CompareTimelinesStatus', () => { }); beforeEach(async () => { - jest.doMock('../../saved_object', () => { + jest.doMock('../saved_object/timelines', () => { return { - getTimeline: mockGetTimeline.mockReturnValue(null), - getTimelineByTemplateTimelineId: mockGetTemplateTimeline.mockReturnValue({ - timeline: [], - }), + getTimelineOrNull: mockGetTimeline.mockReturnValue(null), + getTimelineTemplateOrNull: mockGetTemplateTimeline.mockReturnValue(null), }; }); @@ -447,12 +441,10 @@ describe('CompareTimelinesStatus', () => { }); beforeEach(async () => { - jest.doMock('../../saved_object', () => { + jest.doMock('../saved_object/timelines', () => { return { - getTimeline: mockGetTimeline.mockReturnValue(null), - getTimelineByTemplateTimelineId: mockGetTemplateTimeline.mockReturnValue({ - timeline: [], - }), + getTimelineOrNull: mockGetTimeline.mockReturnValue(null), + getTimelineTemplateOrNull: mockGetTemplateTimeline.mockReturnValue(null), }; }); @@ -535,15 +527,13 @@ describe('CompareTimelinesStatus', () => { }); beforeEach(async () => { - jest.doMock('../../saved_object', () => { + jest.doMock('../saved_object/timelines', () => { return { - getTimeline: mockGetTimeline.mockReturnValue({ + getTimelineOrNull: mockGetTimeline.mockReturnValue({ ...mockGetTimelineValue, status: TimelineStatus.immutable, }), - getTimelineByTemplateTimelineId: mockGetTemplateTimeline.mockReturnValue({ - timeline: [], - }), + getTimelineTemplateOrNull: mockGetTemplateTimeline.mockReturnValue(null), }; }); @@ -609,14 +599,15 @@ describe('CompareTimelinesStatus', () => { }); beforeEach(async () => { - jest.doMock('../../saved_object', () => { + jest.doMock('../saved_object/timelines', () => { return { - getTimeline: mockGetTimeline.mockReturnValue({ + getTimelineOrNull: mockGetTimeline.mockReturnValue({ ...mockGetTemplateTimelineValue, status: TimelineStatus.immutable, }), - getTimelineByTemplateTimelineId: mockGetTemplateTimeline.mockReturnValue({ - timeline: [{ ...mockGetTemplateTimelineValue, status: TimelineStatus.immutable }], + getTimelineTemplateOrNull: mockGetTemplateTimeline.mockReturnValue({ + ...mockGetTemplateTimelineValue, + status: TimelineStatus.immutable, }), }; }); @@ -683,9 +674,9 @@ describe('CompareTimelinesStatus', () => { }); beforeEach(async () => { - jest.doMock('../../saved_object', () => ({ - getTimeline: mockGetTimeline, - getTimelineByTemplateTimelineId: mockGetTemplateTimeline, + jest.doMock('../saved_object/timelines', () => ({ + getTimelineOrNull: mockGetTimeline, + getTimelineTemplateOrNull: mockGetTemplateTimeline, })); const CompareTimelinesStatus = jest.requireActual('./compare_timelines_status') @@ -745,11 +736,11 @@ describe('CompareTimelinesStatus', () => { }); beforeEach(async () => { - jest.doMock('../../saved_object', () => ({ - getTimeline: mockGetTimeline.mockReturnValue(mockGetTemplateTimelineValue), - getTimelineByTemplateTimelineId: mockGetTemplateTimeline.mockReturnValue({ - timeline: [mockGetTemplateTimelineValue], - }), + jest.doMock('../saved_object/timelines', () => ({ + getTimelineOrNull: mockGetTimeline.mockReturnValue(mockGetTemplateTimelineValue), + getTimelineTemplateOrNull: mockGetTemplateTimeline.mockReturnValue( + mockGetTemplateTimelineValue + ), })); const CompareTimelinesStatus = jest.requireActual('./compare_timelines_status') diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/compare_timelines_status.ts b/x-pack/plugins/security_solution/server/lib/timeline/utils/compare_timelines_status.ts similarity index 98% rename from x-pack/plugins/security_solution/server/lib/timeline/routes/utils/compare_timelines_status.ts rename to x-pack/plugins/security_solution/server/lib/timeline/utils/compare_timelines_status.ts index f51f9ed7f87f10..00a31aa04d1bf8 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/compare_timelines_status.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/utils/compare_timelines_status.ts @@ -11,8 +11,8 @@ import { TimelineType, TimelineStatus, TimelineTypeLiteral, -} from '../../../../../common/types/timeline'; -import { FrameworkRequest } from '../../../framework'; +} from '../../../../common/types/timeline'; +import { FrameworkRequest } from '../../framework'; import { TimelineStatusActions, TimelineStatusAction } from './common'; import { TimelineObject } from './timeline_object'; @@ -66,7 +66,6 @@ export class CompareTimelinesStatus { version: templateTimelineInput.version, frameworkRequest, }); - this.timelineType = timelineType ?? TimelineType.default; this.title = title ?? null; this.status = status ?? TimelineStatus.active; diff --git a/x-pack/plugins/security_solution/server/lib/timeline/default_timeline.ts b/x-pack/plugins/security_solution/server/lib/timeline/utils/default_timeline.ts similarity index 88% rename from x-pack/plugins/security_solution/server/lib/timeline/default_timeline.ts rename to x-pack/plugins/security_solution/server/lib/timeline/utils/default_timeline.ts index a40a8b6365c6f1..2f362bb722c633 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/default_timeline.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/utils/default_timeline.ts @@ -5,9 +5,9 @@ * 2.0. */ -import { Direction } from '../../graphql/types'; import { defaultHeaders } from './default_timeline_headers'; -import { SavedTimeline, TimelineType, TimelineStatus } from '../../../common/types/timeline'; +import { SavedTimeline, TimelineType, TimelineStatus } from '../../../../common/types/timeline'; +import { Direction } from '../../../../common/search_strategy'; export const draftTimelineDefaults: SavedTimeline = { columns: defaultHeaders, diff --git a/x-pack/plugins/security_solution/server/lib/timeline/default_timeline_headers.ts b/x-pack/plugins/security_solution/server/lib/timeline/utils/default_timeline_headers.ts similarity index 93% rename from x-pack/plugins/security_solution/server/lib/timeline/default_timeline_headers.ts rename to x-pack/plugins/security_solution/server/lib/timeline/utils/default_timeline_headers.ts index 70e318982a215f..d7f3ca5c008f10 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/default_timeline_headers.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/utils/default_timeline_headers.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { SavedTimeline } from '../../../common/types/timeline'; +import { SavedTimeline } from '../../../../common/types/timeline'; export const defaultColumnHeaderType = 'not-filtered'; diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/failure_cases.test.ts b/x-pack/plugins/security_solution/server/lib/timeline/utils/failure_cases.test.ts similarity index 99% rename from x-pack/plugins/security_solution/server/lib/timeline/routes/utils/failure_cases.test.ts rename to x-pack/plugins/security_solution/server/lib/timeline/utils/failure_cases.test.ts index 6c9f1d0a8b590b..196de3541cc235 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/failure_cases.test.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/utils/failure_cases.test.ts @@ -29,7 +29,7 @@ import { TimelineStatus, TimelineType, TimelineSavedObject, -} from '../../../../../common/types/timeline'; +} from '../../../../common/types/timeline'; import { mockGetTimelineValue, mockGetTemplateTimelineValue } from '../__mocks__/import_timelines'; describe('failure cases', () => { diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/failure_cases.ts b/x-pack/plugins/security_solution/server/lib/timeline/utils/failure_cases.ts similarity index 99% rename from x-pack/plugins/security_solution/server/lib/timeline/routes/utils/failure_cases.ts rename to x-pack/plugins/security_solution/server/lib/timeline/utils/failure_cases.ts index cf05ca93a51679..99365a55a1d611 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/failure_cases.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/utils/failure_cases.ts @@ -10,7 +10,7 @@ import { TimelineSavedObject, TimelineStatus, TimelineTypeLiteral, -} from '../../../../../common/types/timeline'; +} from '../../../../common/types/timeline'; export const UPDATE_TIMELINE_ERROR_MESSAGE = 'You cannot create new timelines with PATCH. Use POST instead.'; diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/timeline_object.ts b/x-pack/plugins/security_solution/server/lib/timeline/utils/timeline_object.ts similarity index 84% rename from x-pack/plugins/security_solution/server/lib/timeline/routes/utils/timeline_object.ts rename to x-pack/plugins/security_solution/server/lib/timeline/utils/timeline_object.ts index 38dda10245a0f4..391f887635cff3 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/timeline_object.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/utils/timeline_object.ts @@ -10,9 +10,9 @@ import { TimelineTypeLiteral, TimelineSavedObject, TimelineStatus, -} from '../../../../../common/types/timeline'; -import { getTimeline, getTemplateTimeline } from './create_timelines'; -import { FrameworkRequest } from '../../../framework'; +} from '../../../../common/types/timeline'; +import { FrameworkRequest } from '../../framework'; +import { getTimelineOrNull, getTimelineTemplateOrNull } from '../saved_object/timelines'; interface TimelineObjectProps { id: string | null | undefined; @@ -47,10 +47,9 @@ export class TimelineObject { this.data = this.id != null ? this.type === TimelineType.template - ? await getTemplateTimeline(this.frameworkRequest, this.id) - : await getTimeline(this.frameworkRequest, this.id) + ? await getTimelineTemplateOrNull(this.frameworkRequest, this.id) + : await getTimelineOrNull(this.frameworkRequest, this.id) : null; - return this.data; } diff --git a/x-pack/plugins/security_solution/server/lib/types.ts b/x-pack/plugins/security_solution/server/lib/types.ts index a8616dc1c57d1a..8b2c1126e929fc 100644 --- a/x-pack/plugins/security_solution/server/lib/types.ts +++ b/x-pack/plugins/security_solution/server/lib/types.ts @@ -14,9 +14,9 @@ import { Hosts } from './hosts'; import { IndexFields } from './index_fields'; import { SourceStatus } from './source_status'; import { Sources } from './sources'; -import { Note } from './note/saved_object'; -import { PinnedEvent } from './pinned_event/saved_object'; -import { Timeline } from './timeline/saved_object'; +import { Notes } from './timeline/saved_object/notes'; +import { PinnedEvent } from './timeline/saved_object/pinned_events'; +import { Timeline } from './timeline/saved_object/timelines'; import { TotalValue, BaseHit, Explanation } from '../../common/detection_engine/types'; export * from './hosts'; @@ -31,7 +31,7 @@ export interface AppBackendLibs extends AppDomainLibs { sources: Sources; sourceStatus: SourceStatus; timeline: Timeline; - note: Note; + note: Notes; pinnedEvent: PinnedEvent; } diff --git a/x-pack/plugins/security_solution/server/routes/index.ts b/x-pack/plugins/security_solution/server/routes/index.ts index 57323b58ff8ca2..488816cc6ad903 100644 --- a/x-pack/plugins/security_solution/server/routes/index.ts +++ b/x-pack/plugins/security_solution/server/routes/index.ts @@ -33,16 +33,16 @@ import { importRulesRoute } from '../lib/detection_engine/routes/rules/import_ru import { exportRulesRoute } from '../lib/detection_engine/routes/rules/export_rules_route'; import { findRulesStatusesRoute } from '../lib/detection_engine/routes/rules/find_rules_status_route'; import { getPrepackagedRulesStatusRoute } from '../lib/detection_engine/routes/rules/get_prepackaged_rules_status_route'; -import { importTimelinesRoute } from '../lib/timeline/routes/import_timelines_route'; -import { exportTimelinesRoute } from '../lib/timeline/routes/export_timelines_route'; -import { createTimelinesRoute } from '../lib/timeline/routes/create_timelines_route'; -import { updateTimelinesRoute } from '../lib/timeline/routes/update_timelines_route'; -import { getDraftTimelinesRoute } from '../lib/timeline/routes/get_draft_timelines_route'; -import { cleanDraftTimelinesRoute } from '../lib/timeline/routes/clean_draft_timelines_route'; +import { importTimelinesRoute } from '../lib/timeline/routes/timelines/import_timelines'; +import { exportTimelinesRoute } from '../lib/timeline/routes/timelines/export_timelines'; +import { createTimelinesRoute } from '../lib/timeline/routes/timelines/create_timelines'; +import { updateTimelinesRoute } from '../lib/timeline/routes/timelines/patch_timelines'; +import { getDraftTimelinesRoute } from '../lib/timeline/routes/draft_timelines/get_draft_timelines'; +import { cleanDraftTimelinesRoute } from '../lib/timeline/routes/draft_timelines/clean_draft_timelines'; import { SetupPlugins } from '../plugin'; import { ConfigType } from '../config'; -import { installPrepackedTimelinesRoute } from '../lib/timeline/routes/install_prepacked_timelines_route'; -import { getTimelineRoute } from '../lib/timeline/routes/get_timeline_route'; +import { installPrepackedTimelinesRoute } from '../lib/timeline/routes/prepackaged_timelines/install_prepackaged_timelines'; +import { getTimelineRoute } from '../lib/timeline/routes/timelines/get_timeline'; export const initRoutes = ( router: SecuritySolutionPluginRouter, diff --git a/x-pack/plugins/security_solution/server/saved_objects.ts b/x-pack/plugins/security_solution/server/saved_objects.ts index 162b6fdd6fd181..d483bd25266afb 100644 --- a/x-pack/plugins/security_solution/server/saved_objects.ts +++ b/x-pack/plugins/security_solution/server/saved_objects.ts @@ -7,9 +7,7 @@ import { CoreSetup } from '../../../../src/core/server'; -import { type as noteType } from './lib/note/saved_object_mappings'; -import { type as pinnedEventType } from './lib/pinned_event/saved_object_mappings'; -import { type as timelineType } from './lib/timeline/saved_object_mappings'; +import { noteType, pinnedEventType, timelineType } from './lib/timeline/saved_object_mappings'; import { type as ruleStatusType } from './lib/detection_engine/rules/saved_object_mappings'; import { type as ruleActionsType } from './lib/detection_engine/rule_actions/saved_object_mappings'; import { type as signalsMigrationType } from './lib/detection_engine/migrations/saved_objects'; From 738425932fdc3e9d6aa10f03874696521caa88d7 Mon Sep 17 00:00:00 2001 From: Devon Thomson Date: Tue, 30 Mar 2021 14:26:03 -0400 Subject: [PATCH 21/32] added submit to customize panel modal so that enter button works (#95704) --- .../customize_title/customize_panel_modal.tsx | 147 +++++++++--------- 1 file changed, 77 insertions(+), 70 deletions(-) diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/customize_title/customize_panel_modal.tsx b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/customize_title/customize_panel_modal.tsx index 6d5c2224c06351..feabc107dcf3d8 100644 --- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/customize_title/customize_panel_modal.tsx +++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/customize_title/customize_panel_modal.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import React, { Component } from 'react'; +import React, { Component, FormEvent } from 'react'; import { EuiFormRow, @@ -72,80 +72,87 @@ export class CustomizePanelModal extends Component {
- - -

Customize panel

-
-
+
{ + event.preventDefault(); + this.save(); + }} + > + + +

Customize panel

+
+
- - - - } - onChange={this.onHideTitleToggle} - /> - - - this.setState({ title: e.target.value })} - aria-label={i18n.translate( - 'embeddableApi.customizePanel.modal.optionsMenuForm.panelTitleInputAriaLabel', + + + + } + onChange={this.onHideTitleToggle} + /> + + - - - } - /> - - - - this.props.cancel()}> - - + > + this.setState({ title: e.target.value })} + aria-label={i18n.translate( + 'embeddableApi.customizePanel.modal.optionsMenuForm.panelTitleInputAriaLabel', + { + defaultMessage: 'Enter a custom title for your panel', + } + )} + append={ + + + + } + /> + + + + this.props.cancel()}> + + - - - - + + + + +
From 53d4fa7052ae3467ea1aaa67843fd98bb9061a1f Mon Sep 17 00:00:00 2001 From: Constance Date: Tue, 30 Mar 2021 11:35:54 -0700 Subject: [PATCH 22/32] [App Search] API logs: Server route + ApiLogsLogic + useEffects (#95732) * Set up API route * Set up API types * Set up date util needed by filters dates * Add ApiLogsLogic * Update ApiLogs and EngineOverview views with polling behavior * Add API type notes - maybe serves as a TODO to clean up our API data some day --- .../components/api_logs/api_logs.test.tsx | 45 ++- .../components/api_logs/api_logs.tsx | 20 +- .../api_logs/api_logs_logic.test.ts | 308 ++++++++++++++++++ .../components/api_logs/api_logs_logic.ts | 156 +++++++++ .../components/api_logs/constants.ts | 10 + .../app_search/components/api_logs/index.ts | 1 + .../app_search/components/api_logs/types.ts | 27 ++ .../components/api_logs/utils.test.ts | 25 ++ .../app_search/components/api_logs/utils.ts | 12 + .../components/recent_api_logs.test.tsx | 13 + .../components/recent_api_logs.tsx | 12 +- .../server/routes/app_search/api_logs.test.ts | 54 +++ .../server/routes/app_search/api_logs.ts | 35 ++ .../server/routes/app_search/index.ts | 2 + 14 files changed, 715 insertions(+), 5 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_logs_logic.test.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_logs_logic.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/types.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/utils.test.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/utils.ts create mode 100644 x-pack/plugins/enterprise_search/server/routes/app_search/api_logs.test.ts create mode 100644 x-pack/plugins/enterprise_search/server/routes/app_search/api_logs.ts diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_logs.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_logs.test.tsx index da57fd466ffe17..7bdfaf87a2b2f2 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_logs.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_logs.test.tsx @@ -5,28 +5,67 @@ * 2.0. */ +import { setMockValues, setMockActions, rerender } from '../../../__mocks__'; +import '../../../__mocks__/shallow_useeffect.mock'; + import React from 'react'; -import { shallow } from 'enzyme'; +import { shallow, ShallowWrapper } from 'enzyme'; import { EuiPageHeader } from '@elastic/eui'; +import { Loading } from '../../../shared/loading'; import { LogRetentionCallout, LogRetentionTooltip } from '../log_retention'; import { ApiLogs } from './'; describe('ApiLogs', () => { + const values = { + dataLoading: false, + apiLogs: [], + meta: { page: { current: 1 } }, + }; + const actions = { + fetchApiLogs: jest.fn(), + pollForApiLogs: jest.fn(), + }; + + let wrapper: ShallowWrapper; + beforeEach(() => { jest.clearAllMocks(); + setMockValues(values); + setMockActions(actions); + wrapper = shallow(); }); it('renders', () => { - const wrapper = shallow(); - expect(wrapper.find(EuiPageHeader).prop('pageTitle')).toEqual('API Logs'); // TODO: Check for ApiLogsTable + NewApiEventsPrompt when those get added expect(wrapper.find(LogRetentionCallout).prop('type')).toEqual('api'); expect(wrapper.find(LogRetentionTooltip).prop('type')).toEqual('api'); }); + + it('renders a loading screen', () => { + setMockValues({ ...values, dataLoading: true, apiLogs: [] }); + rerender(wrapper); + + expect(wrapper.find(Loading)).toHaveLength(1); + }); + + describe('effects', () => { + it('calls a manual fetchApiLogs on page load and pagination', () => { + expect(actions.fetchApiLogs).toHaveBeenCalledTimes(1); + + setMockValues({ ...values, meta: { page: { current: 2 } } }); + rerender(wrapper); + + expect(actions.fetchApiLogs).toHaveBeenCalledTimes(2); + }); + + it('starts pollForApiLogs on page load', () => { + expect(actions.pollForApiLogs).toHaveBeenCalledTimes(1); + }); + }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_logs.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_logs.tsx index 7e3fadb44fc7a9..2ffc9ea303b5c9 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_logs.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_logs.tsx @@ -5,22 +5,40 @@ * 2.0. */ -import React from 'react'; +import React, { useEffect } from 'react'; + +import { useValues, useActions } from 'kea'; import { EuiPageHeader, EuiTitle, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { FlashMessages } from '../../../shared/flash_messages'; import { SetAppSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome'; import { BreadcrumbTrail } from '../../../shared/kibana_chrome/generate_breadcrumbs'; +import { Loading } from '../../../shared/loading'; import { LogRetentionCallout, LogRetentionTooltip, LogRetentionOptions } from '../log_retention'; import { API_LOGS_TITLE, RECENT_API_EVENTS } from './constants'; +import { ApiLogsLogic } from './'; + interface Props { engineBreadcrumb: BreadcrumbTrail; } export const ApiLogs: React.FC = ({ engineBreadcrumb }) => { + const { dataLoading, apiLogs, meta } = useValues(ApiLogsLogic); + const { fetchApiLogs, pollForApiLogs } = useActions(ApiLogsLogic); + + useEffect(() => { + fetchApiLogs(); + }, [meta.page.current]); + + useEffect(() => { + pollForApiLogs(); + }, []); + + if (dataLoading && !apiLogs.length) return ; + return ( <> diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_logs_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_logs_logic.test.ts new file mode 100644 index 00000000000000..e7f3124a48e8c5 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_logs_logic.test.ts @@ -0,0 +1,308 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { LogicMounter, mockHttpValues, mockFlashMessageHelpers } from '../../../__mocks__'; +import '../../__mocks__/engine_logic.mock'; + +import { nextTick } from '@kbn/test/jest'; + +import { DEFAULT_META } from '../../../shared/constants'; + +import { POLLING_ERROR_MESSAGE } from './constants'; + +import { ApiLogsLogic } from './'; + +describe('ApiLogsLogic', () => { + const { mount, unmount } = new LogicMounter(ApiLogsLogic); + const { http } = mockHttpValues; + const { flashAPIErrors, setErrorMessage } = mockFlashMessageHelpers; + + const DEFAULT_VALUES = { + dataLoading: true, + apiLogs: [], + meta: DEFAULT_META, + hasNewData: false, + polledData: {}, + intervalId: null, + }; + + const MOCK_API_RESPONSE = { + results: [ + { + timestamp: '1970-01-01T12:00:00.000Z', + http_method: 'POST', + status: 200, + user_agent: 'some browser agent string', + full_request_path: '/api/as/v1/engines/national-parks-demo/search.json', + request_body: '{"someMockRequest":"hello"}', + response_body: '{"someMockResponse":"world"}', + }, + ], + meta: { + page: { + current: 1, + total_pages: 10, + total_results: 100, + size: 10, + }, + }, + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('has expected default values', () => { + mount(); + expect(ApiLogsLogic.values).toEqual(DEFAULT_VALUES); + }); + + describe('actions', () => { + describe('onPollStart', () => { + it('sets intervalId state', () => { + mount(); + ApiLogsLogic.actions.onPollStart(123); + + expect(ApiLogsLogic.values).toEqual({ + ...DEFAULT_VALUES, + intervalId: 123, + }); + }); + }); + + describe('storePolledData', () => { + it('sets hasNewData to true & polledData state', () => { + mount({ hasNewData: false }); + ApiLogsLogic.actions.storePolledData(MOCK_API_RESPONSE); + + expect(ApiLogsLogic.values).toEqual({ + ...DEFAULT_VALUES, + hasNewData: true, + polledData: MOCK_API_RESPONSE, + }); + }); + }); + + describe('updateView', () => { + it('sets dataLoading & hasNewData to false, sets apiLogs & meta state', () => { + mount({ dataLoading: true, hasNewData: true }); + ApiLogsLogic.actions.updateView(MOCK_API_RESPONSE); + + expect(ApiLogsLogic.values).toEqual({ + ...DEFAULT_VALUES, + dataLoading: false, + hasNewData: false, + apiLogs: MOCK_API_RESPONSE.results, + meta: MOCK_API_RESPONSE.meta, + }); + }); + }); + + describe('onPaginate', () => { + it('sets dataLoading to true & sets meta state', () => { + mount({ dataLoading: false }); + ApiLogsLogic.actions.onPaginate(5); + + expect(ApiLogsLogic.values).toEqual({ + ...DEFAULT_VALUES, + dataLoading: true, + meta: { + ...DEFAULT_META, + page: { + ...DEFAULT_META.page, + current: 5, + }, + }, + }); + }); + }); + }); + + describe('listeners', () => { + describe('pollForApiLogs', () => { + jest.useFakeTimers(); + const setIntervalSpy = jest.spyOn(global, 'setInterval'); + + it('starts a poll that calls fetchApiLogs at set intervals', () => { + mount(); + jest.spyOn(ApiLogsLogic.actions, 'onPollStart'); + jest.spyOn(ApiLogsLogic.actions, 'fetchApiLogs'); + + ApiLogsLogic.actions.pollForApiLogs(); + expect(setIntervalSpy).toHaveBeenCalled(); + expect(ApiLogsLogic.actions.onPollStart).toHaveBeenCalled(); + + jest.advanceTimersByTime(5000); + expect(ApiLogsLogic.actions.fetchApiLogs).toHaveBeenCalledWith({ isPoll: true }); + }); + + it('does not create new polls if one already exists', () => { + mount({ intervalId: 123 }); + ApiLogsLogic.actions.pollForApiLogs(); + expect(setIntervalSpy).not.toHaveBeenCalled(); + }); + + afterAll(() => jest.useRealTimers); + }); + + describe('fetchApiLogs', () => { + const mockDate = jest + .spyOn(global.Date, 'now') + .mockImplementation(() => new Date('1970-01-02').valueOf()); + + afterAll(() => mockDate.mockRestore()); + + it('should make an API call', () => { + mount(); + + ApiLogsLogic.actions.fetchApiLogs(); + + expect(http.get).toHaveBeenCalledWith('/api/app_search/engines/some-engine/api_logs', { + query: { + 'page[current]': 1, + 'filters[date][from]': '1970-01-01T00:00:00.000Z', + 'filters[date][to]': '1970-01-02T00:00:00.000Z', + sort_direction: 'desc', + }, + }); + }); + + describe('manual fetch (page load & pagination)', () => { + it('updates the view immediately with the returned data', async () => { + http.get.mockReturnValueOnce(Promise.resolve(MOCK_API_RESPONSE)); + mount(); + jest.spyOn(ApiLogsLogic.actions, 'updateView'); + + ApiLogsLogic.actions.fetchApiLogs(); + await nextTick(); + + expect(ApiLogsLogic.actions.updateView).toHaveBeenCalledWith(MOCK_API_RESPONSE); + }); + + it('handles API errors', async () => { + http.get.mockReturnValueOnce(Promise.reject('error')); + mount(); + + ApiLogsLogic.actions.fetchApiLogs(); + await nextTick(); + + expect(flashAPIErrors).toHaveBeenCalledWith('error'); + }); + }); + + describe('poll fetch (interval)', () => { + it('does not automatically update the view', async () => { + http.get.mockReturnValueOnce(Promise.resolve(MOCK_API_RESPONSE)); + mount({ dataLoading: false }); + jest.spyOn(ApiLogsLogic.actions, 'onPollInterval'); + + ApiLogsLogic.actions.fetchApiLogs({ isPoll: true }); + await nextTick(); + + expect(ApiLogsLogic.actions.onPollInterval).toHaveBeenCalledWith(MOCK_API_RESPONSE); + }); + + it('sets a custom error message on poll error', async () => { + http.get.mockReturnValueOnce(Promise.reject('error')); + mount({ dataLoading: false }); + + ApiLogsLogic.actions.fetchApiLogs({ isPoll: true }); + await nextTick(); + + expect(setErrorMessage).toHaveBeenCalledWith(POLLING_ERROR_MESSAGE); + }); + }); + + describe('when a manual fetch and a poll fetch occur at the same time', () => { + it('should short-circuit polls in favor of manual fetches', async () => { + // dataLoading is the signal we're using to check for a manual fetch + mount({ dataLoading: true }); + jest.spyOn(ApiLogsLogic.actions, 'onPollInterval'); + + ApiLogsLogic.actions.fetchApiLogs({ isPoll: true }); + await nextTick(); + + expect(http.get).not.toHaveBeenCalled(); + expect(ApiLogsLogic.actions.onPollInterval).not.toHaveBeenCalled(); + }); + }); + }); + + describe('onPollInterval', () => { + describe('when API logs are empty and new polled data comes in', () => { + it('updates the view immediately with the returned data (no manual action required)', () => { + mount({ meta: { page: { total_results: 0 } } }); + jest.spyOn(ApiLogsLogic.actions, 'updateView'); + + ApiLogsLogic.actions.onPollInterval(MOCK_API_RESPONSE); + + expect(ApiLogsLogic.actions.updateView).toHaveBeenCalledWith(MOCK_API_RESPONSE); + }); + }); + + describe('when previous API logs already exist on the page', () => { + describe('when new data is returned', () => { + it('stores the new polled data', () => { + mount({ meta: { page: { total_results: 1 } } }); + jest.spyOn(ApiLogsLogic.actions, 'storePolledData'); + + ApiLogsLogic.actions.onPollInterval(MOCK_API_RESPONSE); + + expect(ApiLogsLogic.actions.storePolledData).toHaveBeenCalledWith(MOCK_API_RESPONSE); + }); + }); + + describe('when the same data is returned', () => { + it('does nothing', () => { + mount({ meta: { page: { total_results: 100 } } }); + jest.spyOn(ApiLogsLogic.actions, 'updateView'); + jest.spyOn(ApiLogsLogic.actions, 'storePolledData'); + + ApiLogsLogic.actions.onPollInterval(MOCK_API_RESPONSE); + + expect(ApiLogsLogic.actions.updateView).not.toHaveBeenCalled(); + expect(ApiLogsLogic.actions.storePolledData).not.toHaveBeenCalled(); + }); + }); + }); + }); + + describe('onUserRefresh', () => { + it('updates the apiLogs data with the stored polled data', () => { + mount({ apiLogs: [], polledData: MOCK_API_RESPONSE }); + + ApiLogsLogic.actions.onUserRefresh(); + + expect(ApiLogsLogic.values).toEqual({ + ...DEFAULT_VALUES, + apiLogs: MOCK_API_RESPONSE.results, + meta: MOCK_API_RESPONSE.meta, + polledData: MOCK_API_RESPONSE, + dataLoading: false, + }); + }); + }); + }); + + describe('events', () => { + describe('unmount', () => { + const clearIntervalSpy = jest.spyOn(global, 'clearInterval'); + + it('clears the poll interval', () => { + mount({ intervalId: 123 }); + unmount(); + expect(clearIntervalSpy).toHaveBeenCalledWith(123); + }); + + it('does not clearInterval if a poll has not been started', () => { + mount({ intervalId: null }); + unmount(); + expect(clearIntervalSpy).not.toHaveBeenCalled(); + }); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_logs_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_logs_logic.ts new file mode 100644 index 00000000000000..2a2f55a0c80331 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_logs_logic.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { kea, MakeLogicType } from 'kea'; + +import { DEFAULT_META } from '../../../shared/constants'; +import { flashAPIErrors, setErrorMessage } from '../../../shared/flash_messages'; +import { HttpLogic } from '../../../shared/http'; +import { updateMetaPageIndex } from '../../../shared/table_pagination'; +import { EngineLogic } from '../engine'; + +import { POLLING_DURATION, POLLING_ERROR_MESSAGE } from './constants'; +import { ApiLogsData, ApiLog } from './types'; +import { getDateString } from './utils'; + +interface ApiLogsValues { + dataLoading: boolean; + apiLogs: ApiLog[]; + meta: ApiLogsData['meta']; + hasNewData: boolean; + polledData: ApiLogsData; + intervalId: number | null; +} + +interface ApiLogsActions { + fetchApiLogs(options?: { isPoll: boolean }): { isPoll: boolean }; + pollForApiLogs(): void; + onPollStart(intervalId: number): { intervalId: number }; + onPollInterval(data: ApiLogsData): ApiLogsData; + storePolledData(data: ApiLogsData): ApiLogsData; + updateView(data: ApiLogsData): ApiLogsData; + onUserRefresh(): void; + onPaginate(newPageIndex: number): { newPageIndex: number }; +} + +export const ApiLogsLogic = kea>({ + path: ['enterprise_search', 'app_search', 'api_logs_logic'], + actions: () => ({ + fetchApiLogs: ({ isPoll } = { isPoll: false }) => ({ isPoll }), + pollForApiLogs: true, + onPollStart: (intervalId) => ({ intervalId }), + onPollInterval: ({ results, meta }) => ({ results, meta }), + storePolledData: ({ results, meta }) => ({ results, meta }), + updateView: ({ results, meta }) => ({ results, meta }), + onUserRefresh: true, + onPaginate: (newPageIndex) => ({ newPageIndex }), + }), + reducers: () => ({ + dataLoading: [ + true, + { + updateView: () => false, + onPaginate: () => true, + }, + ], + apiLogs: [ + [], + { + updateView: (_, { results }) => results, + }, + ], + meta: [ + DEFAULT_META, + { + updateView: (_, { meta }) => meta, + onPaginate: (state, { newPageIndex }) => updateMetaPageIndex(state, newPageIndex), + }, + ], + hasNewData: [ + false, + { + storePolledData: () => true, + updateView: () => false, + }, + ], + polledData: [ + {} as ApiLogsData, + { + storePolledData: (_, data) => data, + }, + ], + intervalId: [ + null, + { + onPollStart: (_, { intervalId }) => intervalId, + }, + ], + }), + listeners: ({ actions, values }) => ({ + pollForApiLogs: () => { + if (values.intervalId) return; // Ensure we only have one poll at a time + + const id = window.setInterval(() => actions.fetchApiLogs({ isPoll: true }), POLLING_DURATION); + actions.onPollStart(id); + }, + fetchApiLogs: async ({ isPoll }) => { + if (isPoll && values.dataLoading) return; // Manual fetches (i.e. user pagination) should override polling + + const { http } = HttpLogic.values; + const { engineName } = EngineLogic.values; + + try { + const response = await http.get(`/api/app_search/engines/${engineName}/api_logs`, { + query: { + 'page[current]': values.meta.page.current, + 'filters[date][from]': getDateString(-1), + 'filters[date][to]': getDateString(), + sort_direction: 'desc', + }, + }); + + // Manual fetches (e.g. page load, user pagination) should update the view immediately, + // while polls are stored in-state until the user manually triggers the 'Refresh' action + if (isPoll) { + actions.onPollInterval(response); + } else { + actions.updateView(response); + } + } catch (e) { + if (isPoll) { + // If polling fails, it will typically be due due to http connection - + // we should send a more human-readable message if so + setErrorMessage(POLLING_ERROR_MESSAGE); + } else { + flashAPIErrors(e); + } + } + }, + onPollInterval: (data, breakpoint) => { + breakpoint(); // Prevents errors if logic unmounts while fetching + + const previousResults = values.meta.page.total_results; + const newResults = data.meta.page.total_results; + const isEmpty = previousResults === 0; + const hasNewData = previousResults !== newResults; + + if (isEmpty && hasNewData) { + actions.updateView(data); // Empty logs should automatically update with new data without a manual action + } else if (hasNewData) { + actions.storePolledData(data); // Otherwise, store any new data until the user manually refreshes the table + } + }, + onUserRefresh: () => { + actions.updateView(values.polledData); + }, + }), + events: ({ values }) => ({ + beforeUnmount() { + if (values.intervalId !== null) clearInterval(values.intervalId); + }, + }), +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/constants.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/constants.ts index 1620b0a953d465..9f64ec44e8b134 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/constants.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/constants.ts @@ -16,3 +16,13 @@ export const RECENT_API_EVENTS = i18n.translate( 'xpack.enterpriseSearch.appSearch.engine.apiLogs.recent', { defaultMessage: 'Recent API events' } ); + +export const POLLING_DURATION = 5000; + +export const POLLING_ERROR_MESSAGE = i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.apiLogs.pollingErrorMessage', + { + defaultMessage: + 'Could not automatically refresh API logs data. Please check your connection or manually refresh the page.', + } +); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/index.ts index 104ae03b892203..dc05fe3de0d5c8 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/index.ts @@ -7,3 +7,4 @@ export { API_LOGS_TITLE } from './constants'; export { ApiLogs } from './api_logs'; +export { ApiLogsLogic } from './api_logs_logic'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/types.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/types.ts new file mode 100644 index 00000000000000..05c0d11d032402 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/types.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Meta } from '../../../../../common/types'; + +export interface ApiLog { + timestamp: string; // Date ISO string + status: number; + http_method: string; + full_request_path: string; + user_agent: string; + request_body: string; // JSON string + response_body: string; // JSON string + // NOTE: The API also sends us back `path: null`, but we don't appear to be + // using it anywhere, so I've opted not to list it in our types +} + +export interface ApiLogsData { + results: ApiLog[]; + meta: Meta; + // NOTE: The API sends us back even more `meta` data than the normal (sort_direction, filters, query), + // but we currently don't use that data in our front-end code, so I'm opting not to list them in our types +} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/utils.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/utils.test.ts new file mode 100644 index 00000000000000..53c210d5952916 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/utils.test.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { getDateString } from './utils'; + +describe('getDateString', () => { + const mockDate = jest + .spyOn(global.Date, 'now') + .mockImplementation(() => new Date('1970-01-02').valueOf()); + + it('gets the current date in ISO format', () => { + expect(getDateString()).toEqual('1970-01-02T00:00:00.000Z'); + }); + + it('allows passing a number of days to offset the timestamp by', () => { + expect(getDateString(-1)).toEqual('1970-01-01T00:00:00.000Z'); + expect(getDateString(10)).toEqual('1970-01-12T00:00:00.000Z'); + }); + + afterAll(() => mockDate.mockRestore()); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/utils.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/utils.ts new file mode 100644 index 00000000000000..4e2dfc2cf701af --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/utils.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const getDateString = (offSetDays?: number) => { + const date = new Date(Date.now()); + if (offSetDays) date.setDate(date.getDate() + offSetDays); + return date.toISOString(); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/recent_api_logs.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/recent_api_logs.test.tsx index ceda3ab92f589b..44e125221f6742 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/recent_api_logs.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/recent_api_logs.test.tsx @@ -5,6 +5,8 @@ * 2.0. */ +import { setMockActions } from '../../../../__mocks__'; +import '../../../../__mocks__/shallow_useeffect.mock'; import '../../../__mocks__/engine_logic.mock'; import React from 'react'; @@ -14,10 +16,16 @@ import { shallow, ShallowWrapper } from 'enzyme'; import { RecentApiLogs } from './recent_api_logs'; describe('RecentApiLogs', () => { + const actions = { + fetchApiLogs: jest.fn(), + pollForApiLogs: jest.fn(), + }; + let wrapper: ShallowWrapper; beforeAll(() => { jest.clearAllMocks(); + setMockActions(actions); wrapper = shallow(); }); @@ -25,4 +33,9 @@ describe('RecentApiLogs', () => { expect(wrapper.prop('title')).toEqual(

Recent API events

); // TODO: expect(wrapper.find(ApiLogsTable)).toHaveLength(1) }); + + it('calls fetchApiLogs on page load and starts pollForApiLogs', () => { + expect(actions.fetchApiLogs).toHaveBeenCalledTimes(1); + expect(actions.pollForApiLogs).toHaveBeenCalledTimes(1); + }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/recent_api_logs.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/recent_api_logs.tsx index 1c7f43a5925362..e77a4ad7b03487 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/recent_api_logs.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/recent_api_logs.tsx @@ -5,10 +5,13 @@ * 2.0. */ -import React from 'react'; +import React, { useEffect } from 'react'; + +import { useActions } from 'kea'; import { EuiButtonEmptyTo } from '../../../../shared/react_router_helpers'; import { ENGINE_API_LOGS_PATH } from '../../../routes'; +import { ApiLogsLogic } from '../../api_logs'; import { RECENT_API_EVENTS } from '../../api_logs/constants'; import { DataPanel } from '../../data_panel'; import { generateEnginePath } from '../../engine'; @@ -16,6 +19,13 @@ import { generateEnginePath } from '../../engine'; import { VIEW_API_LOGS } from '../constants'; export const RecentApiLogs: React.FC = () => { + const { fetchApiLogs, pollForApiLogs } = useActions(ApiLogsLogic); + + useEffect(() => { + fetchApiLogs(); + pollForApiLogs(); + }, []); + return ( {RECENT_API_EVENTS}} diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/api_logs.test.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/api_logs.test.ts new file mode 100644 index 00000000000000..3152b371c2fbb0 --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/api_logs.test.ts @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { MockRouter, mockRequestHandler, mockDependencies } from '../../__mocks__'; + +import { registerApiLogsRoutes } from './api_logs'; + +describe('API logs routes', () => { + describe('GET /api/app_search/engines/{engineName}/api_logs', () => { + let mockRouter: MockRouter; + + beforeEach(() => { + jest.clearAllMocks(); + mockRouter = new MockRouter({ + method: 'get', + path: '/api/app_search/engines/{engineName}/api_logs', + }); + + registerApiLogsRoutes({ + ...mockDependencies, + router: mockRouter.router, + }); + }); + + it('creates a request handler', () => { + expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ + path: '/as/engines/:engineName/api_logs/collection', + }); + }); + + describe('validates', () => { + it('with required query params', () => { + const request = { + query: { + 'filters[date][from]': '1970-01-01T12:00:00.000Z', + 'filters[date][to]': '1970-01-02T12:00:00.000Z', + 'page[current]': 1, + sort_direction: 'desc', + }, + }; + mockRouter.shouldValidate(request); + }); + + it('missing params', () => { + const request = { query: {} }; + mockRouter.shouldThrow(request); + }); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/api_logs.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/api_logs.ts new file mode 100644 index 00000000000000..d57ecb29294beb --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/api_logs.ts @@ -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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { schema } from '@kbn/config-schema'; + +import { RouteDependencies } from '../../plugin'; + +export function registerApiLogsRoutes({ + router, + enterpriseSearchRequestHandler, +}: RouteDependencies) { + router.get( + { + path: '/api/app_search/engines/{engineName}/api_logs', + validate: { + params: schema.object({ + engineName: schema.string(), + }), + query: schema.object({ + 'filters[date][from]': schema.string(), // Date string, expected format: ISO string + 'filters[date][to]': schema.string(), // Date string, expected format: ISO string + 'page[current]': schema.number(), + sort_direction: schema.string(), + }), + }, + }, + enterpriseSearchRequestHandler.createRequest({ + path: '/as/engines/:engineName/api_logs/collection', + }) + ); +} diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/index.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/index.ts index 3c8501ec15b3d5..1d48614e733741 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/index.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/index.ts @@ -8,6 +8,7 @@ import { RouteDependencies } from '../../plugin'; import { registerAnalyticsRoutes } from './analytics'; +import { registerApiLogsRoutes } from './api_logs'; import { registerCredentialsRoutes } from './credentials'; import { registerCurationsRoutes } from './curations'; import { registerDocumentsRoutes, registerDocumentRoutes } from './documents'; @@ -29,5 +30,6 @@ export const registerAppSearchRoutes = (dependencies: RouteDependencies) => { registerSearchSettingsRoutes(dependencies); registerRoleMappingsRoutes(dependencies); registerResultSettingsRoutes(dependencies); + registerApiLogsRoutes(dependencies); registerOnboardingRoutes(dependencies); }; From 9c21f41da9a8e5fbe0d1c6ccd37d98b8aae4f515 Mon Sep 17 00:00:00 2001 From: Kevin Logan <56395104+kevinlog@users.noreply.github.com> Date: Tue, 30 Mar 2021 14:52:02 -0400 Subject: [PATCH 23/32] add linux and mac diagnostics switches (#95819) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../policy/models/advanced_policy_schema.ts | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/models/advanced_policy_schema.ts b/x-pack/plugins/security_solution/public/management/pages/policy/models/advanced_policy_schema.ts index b5bf8594e7a8db..3c760545539c15 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/models/advanced_policy_schema.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/models/advanced_policy_schema.ts @@ -595,6 +595,28 @@ export const AdvancedPolicySchema: AdvancedPolicySchemaType[] = [ } ), }, + { + key: 'linux.advanced.diagnostic.enabled', + first_supported_version: '7.12', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.linux.advanced.diagnostic.enabled', + { + defaultMessage: + "A value of 'false' disables running diagnostic features on Endpoint. Default: true.", + } + ), + }, + { + key: 'mac.advanced.diagnostic.enabled', + first_supported_version: '7.12', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.mac.advanced.diagnostic.enabled', + { + defaultMessage: + "A value of 'false' disables running diagnostic features on Endpoint. Default: true.", + } + ), + }, { key: 'windows.advanced.alerts.cloud_lookup', first_supported_version: '7.12', From d6370f4e5110f2a85b6980f561a0fd6f9419e0ff Mon Sep 17 00:00:00 2001 From: Bryan Clement Date: Tue, 30 Mar 2021 12:09:23 -0700 Subject: [PATCH 24/32] updated osquery autocomplete to v4.7.0 schema (#95741) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- x-pack/plugins/osquery/public/editor/osquery_schema/v4.7.0.json | 1 + x-pack/plugins/osquery/public/editor/osquery_tables.ts | 2 +- x-pack/plugins/osquery/scripts/schema_formatter/script.ts | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 x-pack/plugins/osquery/public/editor/osquery_schema/v4.7.0.json diff --git a/x-pack/plugins/osquery/public/editor/osquery_schema/v4.7.0.json b/x-pack/plugins/osquery/public/editor/osquery_schema/v4.7.0.json new file mode 100644 index 00000000000000..eac29577051a44 --- /dev/null +++ b/x-pack/plugins/osquery/public/editor/osquery_schema/v4.7.0.json @@ -0,0 +1 @@ +[{"name":"account_policy_data"},{"name":"acpi_tables"},{"name":"ad_config"},{"name":"alf"},{"name":"alf_exceptions"},{"name":"alf_explicit_auths"},{"name":"app_schemes"},{"name":"apparmor_events"},{"name":"apparmor_profiles"},{"name":"appcompat_shims"},{"name":"apps"},{"name":"apt_sources"},{"name":"arp_cache"},{"name":"asl"},{"name":"atom_packages"},{"name":"augeas"},{"name":"authenticode"},{"name":"authorization_mechanisms"},{"name":"authorizations"},{"name":"authorized_keys"},{"name":"autoexec"},{"name":"azure_instance_metadata"},{"name":"azure_instance_tags"},{"name":"background_activities_moderator"},{"name":"battery"},{"name":"bitlocker_info"},{"name":"block_devices"},{"name":"bpf_process_events"},{"name":"bpf_socket_events"},{"name":"browser_plugins"},{"name":"carbon_black_info"},{"name":"carves"},{"name":"certificates"},{"name":"chassis_info"},{"name":"chocolatey_packages"},{"name":"chrome_extension_content_scripts"},{"name":"chrome_extensions"},{"name":"connectivity"},{"name":"cpu_info"},{"name":"cpu_time"},{"name":"cpuid"},{"name":"crashes"},{"name":"crontab"},{"name":"cups_destinations"},{"name":"cups_jobs"},{"name":"curl"},{"name":"curl_certificate"},{"name":"deb_packages"},{"name":"default_environment"},{"name":"device_file"},{"name":"device_firmware"},{"name":"device_hash"},{"name":"device_partitions"},{"name":"disk_encryption"},{"name":"disk_events"},{"name":"disk_info"},{"name":"dns_cache"},{"name":"dns_resolvers"},{"name":"docker_container_fs_changes"},{"name":"docker_container_labels"},{"name":"docker_container_mounts"},{"name":"docker_container_networks"},{"name":"docker_container_ports"},{"name":"docker_container_processes"},{"name":"docker_container_stats"},{"name":"docker_containers"},{"name":"docker_image_history"},{"name":"docker_image_labels"},{"name":"docker_image_layers"},{"name":"docker_images"},{"name":"docker_info"},{"name":"docker_network_labels"},{"name":"docker_networks"},{"name":"docker_version"},{"name":"docker_volume_labels"},{"name":"docker_volumes"},{"name":"drivers"},{"name":"ec2_instance_metadata"},{"name":"ec2_instance_tags"},{"name":"elf_dynamic"},{"name":"elf_info"},{"name":"elf_sections"},{"name":"elf_segments"},{"name":"elf_symbols"},{"name":"etc_hosts"},{"name":"etc_protocols"},{"name":"etc_services"},{"name":"event_taps"},{"name":"example"},{"name":"extended_attributes"},{"name":"fan_speed_sensors"},{"name":"fbsd_kmods"},{"name":"file"},{"name":"file_events"},{"name":"firefox_addons"},{"name":"gatekeeper"},{"name":"gatekeeper_approved_apps"},{"name":"groups"},{"name":"hardware_events"},{"name":"hash"},{"name":"homebrew_packages"},{"name":"hvci_status"},{"name":"ibridge_info"},{"name":"ie_extensions"},{"name":"intel_me_info"},{"name":"interface_addresses"},{"name":"interface_details"},{"name":"interface_ipv6"},{"name":"iokit_devicetree"},{"name":"iokit_registry"},{"name":"iptables"},{"name":"kernel_extensions"},{"name":"kernel_info"},{"name":"kernel_modules"},{"name":"kernel_panics"},{"name":"keychain_acls"},{"name":"keychain_items"},{"name":"known_hosts"},{"name":"kva_speculative_info"},{"name":"last"},{"name":"launchd"},{"name":"launchd_overrides"},{"name":"listening_ports"},{"name":"lldp_neighbors"},{"name":"load_average"},{"name":"location_services"},{"name":"logged_in_users"},{"name":"logical_drives"},{"name":"logon_sessions"},{"name":"lxd_certificates"},{"name":"lxd_cluster"},{"name":"lxd_cluster_members"},{"name":"lxd_images"},{"name":"lxd_instance_config"},{"name":"lxd_instance_devices"},{"name":"lxd_instances"},{"name":"lxd_networks"},{"name":"lxd_storage_pools"},{"name":"magic"},{"name":"managed_policies"},{"name":"md_devices"},{"name":"md_drives"},{"name":"md_personalities"},{"name":"mdfind"},{"name":"mdls"},{"name":"memory_array_mapped_addresses"},{"name":"memory_arrays"},{"name":"memory_device_mapped_addresses"},{"name":"memory_devices"},{"name":"memory_error_info"},{"name":"memory_info"},{"name":"memory_map"},{"name":"mounts"},{"name":"msr"},{"name":"nfs_shares"},{"name":"npm_packages"},{"name":"ntdomains"},{"name":"ntfs_acl_permissions"},{"name":"ntfs_journal_events"},{"name":"nvram"},{"name":"oem_strings"},{"name":"office_mru"},{"name":"os_version"},{"name":"osquery_events"},{"name":"osquery_extensions"},{"name":"osquery_flags"},{"name":"osquery_info"},{"name":"osquery_packs"},{"name":"osquery_registry"},{"name":"osquery_schedule"},{"name":"package_bom"},{"name":"package_install_history"},{"name":"package_receipts"},{"name":"patches"},{"name":"pci_devices"},{"name":"physical_disk_performance"},{"name":"pipes"},{"name":"pkg_packages"},{"name":"platform_info"},{"name":"plist"},{"name":"portage_keywords"},{"name":"portage_packages"},{"name":"portage_use"},{"name":"power_sensors"},{"name":"powershell_events"},{"name":"preferences"},{"name":"process_envs"},{"name":"process_events"},{"name":"process_file_events"},{"name":"process_memory_map"},{"name":"process_namespaces"},{"name":"process_open_files"},{"name":"process_open_pipes"},{"name":"process_open_sockets"},{"name":"processes"},{"name":"programs"},{"name":"prometheus_metrics"},{"name":"python_packages"},{"name":"quicklook_cache"},{"name":"registry"},{"name":"routes"},{"name":"rpm_package_files"},{"name":"rpm_packages"},{"name":"running_apps"},{"name":"safari_extensions"},{"name":"sandboxes"},{"name":"scheduled_tasks"},{"name":"screenlock"},{"name":"selinux_events"},{"name":"selinux_settings"},{"name":"services"},{"name":"shadow"},{"name":"shared_folders"},{"name":"shared_memory"},{"name":"shared_resources"},{"name":"sharing_preferences"},{"name":"shell_history"},{"name":"shellbags"},{"name":"shimcache"},{"name":"signature"},{"name":"sip_config"},{"name":"smart_drive_info"},{"name":"smbios_tables"},{"name":"smc_keys"},{"name":"socket_events"},{"name":"ssh_configs"},{"name":"startup_items"},{"name":"sudoers"},{"name":"suid_bin"},{"name":"syslog_events"},{"name":"system_controls"},{"name":"system_extensions"},{"name":"system_info"},{"name":"systemd_units"},{"name":"temperature_sensors"},{"name":"time"},{"name":"time_machine_backups"},{"name":"time_machine_destinations"},{"name":"ulimit_info"},{"name":"uptime"},{"name":"usb_devices"},{"name":"user_events"},{"name":"user_groups"},{"name":"user_interaction_events"},{"name":"user_ssh_keys"},{"name":"userassist"},{"name":"users"},{"name":"video_info"},{"name":"virtual_memory_info"},{"name":"wifi_networks"},{"name":"wifi_status"},{"name":"wifi_survey"},{"name":"winbaseobj"},{"name":"windows_crashes"},{"name":"windows_eventlog"},{"name":"windows_events"},{"name":"windows_optional_features"},{"name":"windows_security_center"},{"name":"windows_security_products"},{"name":"wmi_bios_info"},{"name":"wmi_cli_event_consumers"},{"name":"wmi_event_filters"},{"name":"wmi_filter_consumer_binding"},{"name":"wmi_script_event_consumers"},{"name":"xprotect_entries"},{"name":"xprotect_meta"},{"name":"xprotect_reports"},{"name":"yara"},{"name":"yara_events"},{"name":"ycloud_instance_metadata"},{"name":"yum_sources"}] \ No newline at end of file diff --git a/x-pack/plugins/osquery/public/editor/osquery_tables.ts b/x-pack/plugins/osquery/public/editor/osquery_tables.ts index 8fdacbdf3574e4..d114cda742f9de 100644 --- a/x-pack/plugins/osquery/public/editor/osquery_tables.ts +++ b/x-pack/plugins/osquery/public/editor/osquery_tables.ts @@ -20,7 +20,7 @@ let osqueryTables: TablesJSON | null = null; export const getOsqueryTables = () => { if (!osqueryTables) { // eslint-disable-next-line @typescript-eslint/no-var-requires - osqueryTables = normalizeTables(require('./osquery_schema/v4.6.0.json')); + osqueryTables = normalizeTables(require('./osquery_schema/v4.7.0.json')); } return osqueryTables; }; diff --git a/x-pack/plugins/osquery/scripts/schema_formatter/script.ts b/x-pack/plugins/osquery/scripts/schema_formatter/script.ts index 146bd4a9d49d7a..578c4a11209629 100644 --- a/x-pack/plugins/osquery/scripts/schema_formatter/script.ts +++ b/x-pack/plugins/osquery/scripts/schema_formatter/script.ts @@ -15,7 +15,7 @@ interface DestField { run( async ({ flags }) => { - const schemaPath = path.resolve('../../public/editor/osquery_schema/'); + const schemaPath = path.resolve('./public/editor/osquery_schema/'); const schemaFile = path.join(schemaPath, flags.schema_version as string); const schemaData = await require(schemaFile); // eslint-disable-next-line @typescript-eslint/no-explicit-any From a1bc9a57bd67600396f919cc36bd88f83a9a4866 Mon Sep 17 00:00:00 2001 From: Quynh Nguyen <43350163+qn895@users.noreply.github.com> Date: Tue, 30 Mar 2021 14:35:23 -0500 Subject: [PATCH 25/32] [ML] Fix Anomaly Detection wizard full time range chart blank with saved search containing runtime fields (#95700) * [ML] Fix AD wizard full time range chart broken with saved search * [ML] Update runtimeMappingsSchema to be its own thing for better reuse * [ML] Remove undefined check --- .../ml/common/util/runtime_field_utils.ts | 7 ++---- .../chart_loader.ts | 1 + .../common/chart_loader/chart_loader.ts | 2 ++ .../metric_selection_summary.tsx | 1 + .../components/time_range_step/time_range.tsx | 1 + .../jobs/new_job/recognize/page.tsx | 4 ++++ .../results_service/results_service.d.ts | 2 ++ .../results_service/results_service.js | 8 +++++++ .../routes/schemas/data_visualizer_schema.ts | 16 +------------ .../routes/schemas/fields_service_schema.ts | 3 ++- .../routes/schemas/job_service_schema.ts | 5 ++-- .../routes/schemas/job_validation_schema.ts | 3 ++- .../routes/schemas/runtime_mappings_schema.ts | 23 +++++++++++++++++++ 13 files changed, 52 insertions(+), 24 deletions(-) create mode 100644 x-pack/plugins/ml/server/routes/schemas/runtime_mappings_schema.ts diff --git a/x-pack/plugins/ml/common/util/runtime_field_utils.ts b/x-pack/plugins/ml/common/util/runtime_field_utils.ts index 340f3747472539..06a1960cc943bb 100644 --- a/x-pack/plugins/ml/common/util/runtime_field_utils.ts +++ b/x-pack/plugins/ml/common/util/runtime_field_utils.ts @@ -6,10 +6,7 @@ */ import { isPopulatedObject } from './object_utils'; -import { - RUNTIME_FIELD_TYPES, - RuntimeType, -} from '../../../../../src/plugins/data/common/index_patterns'; +import { RUNTIME_FIELD_TYPES } from '../../../../../src/plugins/data/common/index_patterns'; import type { RuntimeField, RuntimeMappings } from '../types/fields'; export function isRuntimeField(arg: unknown): arg is RuntimeField { @@ -24,7 +21,7 @@ export function isRuntimeField(arg: unknown): arg is RuntimeField { Object.keys(arg.script).length === 1 && arg.script.hasOwnProperty('source') && typeof arg.script.source === 'string')))) && - RUNTIME_FIELD_TYPES.includes(arg.type as RuntimeType) + RUNTIME_FIELD_TYPES.includes(arg.type) ); } diff --git a/x-pack/plugins/ml/public/application/components/model_snapshots/revert_model_snapshot_flyout/chart_loader.ts b/x-pack/plugins/ml/public/application/components/model_snapshots/revert_model_snapshot_flyout/chart_loader.ts index 4788254e97d1e8..15979751f40d69 100644 --- a/x-pack/plugins/ml/public/application/components/model_snapshots/revert_model_snapshot_flyout/chart_loader.ts +++ b/x-pack/plugins/ml/public/application/components/model_snapshots/revert_model_snapshot_flyout/chart_loader.ts @@ -30,6 +30,7 @@ export function chartLoaderProvider(mlResultsService: MlResultsService) { job.data_counts.earliest_record_timestamp, job.data_counts.latest_record_timestamp, intervalMs, + job.datafeed_config.runtime_mappings, // @ts-expect-error @elastic/elasticsearch Datafeed is missing indices_options job.datafeed_config.indices_options ); diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/common/chart_loader/chart_loader.ts b/x-pack/plugins/ml/public/application/jobs/new_job/common/chart_loader/chart_loader.ts index ddd2aa36194722..9c8f34260def03 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/common/chart_loader/chart_loader.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/common/chart_loader/chart_loader.ts @@ -128,6 +128,7 @@ export class ChartLoader { start: number, end: number, intervalMs: number, + runtimeMappings?: RuntimeMappings, indicesOptions?: IndicesOptions ): Promise { if (this._timeFieldName !== '') { @@ -138,6 +139,7 @@ export class ChartLoader { start, end, intervalMs * 3, + runtimeMappings, indicesOptions ); if (resp.error !== undefined) { diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/metric_selection_summary.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/metric_selection_summary.tsx index f3396a95738a6e..c553da93a4bc0a 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/metric_selection_summary.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/metric_selection_summary.tsx @@ -55,6 +55,7 @@ export const CategorizationDetectorsSummary: FC = () => { jobCreator.start, jobCreator.end, chartInterval.getInterval().asMilliseconds(), + jobCreator.runtimeMappings ?? undefined, // @ts-expect-error @elastic/elasticsearch Datafeed is missing indices_options jobCreator.datafeedConfig.indices_options ); diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/time_range_step/time_range.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/time_range_step/time_range.tsx index b57fd45019abe0..7e7b919f677ab1 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/time_range_step/time_range.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/time_range_step/time_range.tsx @@ -48,6 +48,7 @@ export const TimeRangeStep: FC = ({ setCurrentStep, isCurrentStep }) jobCreator.start, jobCreator.end, chartInterval.getInterval().asMilliseconds(), + jobCreator.runtimeMappings ?? undefined, // @ts-expect-error @elastic/elasticsearch Datafeed is missing indices_options jobCreator.datafeedConfig.indices_options ); diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/recognize/page.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/recognize/page.tsx index 271898654ca497..69fe66b4ca190b 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/recognize/page.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/recognize/page.tsx @@ -44,6 +44,8 @@ import { JobId } from '../../../../../common/types/anomaly_detection_jobs'; import { ML_PAGES } from '../../../../../common/constants/ml_url_generator'; import { TIME_FORMAT } from '../../../../../common/constants/time_format'; import { JobsAwaitingNodeWarning } from '../../../components/jobs_awaiting_node_warning'; +import { isPopulatedObject } from '../../../../../common/util/object_utils'; +import { RuntimeMappings } from '../../../../../common/types/fields'; export interface ModuleJobUI extends ModuleJob { datafeedResult?: DatafeedResponse; @@ -133,10 +135,12 @@ export const Page: FC = ({ moduleId, existingGroupIds }) => { timeRange: TimeRange ): Promise => { if (useFullIndexData) { + const runtimeMappings = indexPattern.getComputedFields().runtimeFields as RuntimeMappings; const { start, end } = await ml.getTimeFieldRange({ index: indexPattern.title, timeFieldName: indexPattern.timeFieldName, query: combinedQuery, + ...(isPopulatedObject(runtimeMappings) ? { runtimeMappings } : {}), }); return { start: start.epoch, diff --git a/x-pack/plugins/ml/public/application/services/results_service/results_service.d.ts b/x-pack/plugins/ml/public/application/services/results_service/results_service.d.ts index bb0cdc89904f88..d26e650d145cb6 100644 --- a/x-pack/plugins/ml/public/application/services/results_service/results_service.d.ts +++ b/x-pack/plugins/ml/public/application/services/results_service/results_service.d.ts @@ -10,6 +10,7 @@ import { MlApiServices } from '../ml_api_service'; import type { AnomalyRecordDoc } from '../../../../common/types/anomalies'; import { InfluencersFilterQuery } from '../../../../common/types/es_client'; import { EntityField } from '../../../../common/util/anomaly_utils'; +import { RuntimeMappings } from '../../../../common/types/fields'; type RecordForInfluencer = AnomalyRecordDoc; export function resultsServiceProvider( @@ -64,6 +65,7 @@ export function resultsServiceProvider( earliestMs: number, latestMs: number, intervalMs: number, + runtimeMappings?: RuntimeMappings, indicesOptions?: IndicesOptions ): Promise; getEventDistributionData( diff --git a/x-pack/plugins/ml/public/application/services/results_service/results_service.js b/x-pack/plugins/ml/public/application/services/results_service/results_service.js index fa0bcd6ea987d4..b041267f46c041 100644 --- a/x-pack/plugins/ml/public/application/services/results_service/results_service.js +++ b/x-pack/plugins/ml/public/application/services/results_service/results_service.js @@ -14,6 +14,7 @@ import { SWIM_LANE_DEFAULT_PAGE_SIZE, } from '../../explorer/explorer_constants'; import { aggregationTypeTransform } from '../../../../common/util/anomaly_utils'; +import { isPopulatedObject } from '../../../../common/util/object_utils'; /** * Service for carrying out Elasticsearch queries to obtain data for the Ml Results dashboards. @@ -1059,6 +1060,7 @@ export function resultsServiceProvider(mlApiServices) { earliestMs, latestMs, intervalMs, + runtimeMappings, indicesOptions ) { return new Promise((resolve, reject) => { @@ -1109,6 +1111,12 @@ export function resultsServiceProvider(mlApiServices) { }, }, }, + // Runtime mappings only needed to support when query includes a runtime field + // even though the default timeField can be a search time runtime field + // because currently Kibana doesn't support that + ...(isPopulatedObject(runtimeMappings) && query + ? { runtime_mappings: runtimeMappings } + : {}), }, ...(indicesOptions ?? {}), }) diff --git a/x-pack/plugins/ml/server/routes/schemas/data_visualizer_schema.ts b/x-pack/plugins/ml/server/routes/schemas/data_visualizer_schema.ts index c3fad2f53e2606..0d7e55d29b1c5c 100644 --- a/x-pack/plugins/ml/server/routes/schemas/data_visualizer_schema.ts +++ b/x-pack/plugins/ml/server/routes/schemas/data_visualizer_schema.ts @@ -6,27 +6,13 @@ */ import { schema } from '@kbn/config-schema'; -import { isRuntimeField } from '../../../common/util/runtime_field_utils'; +import { runtimeMappingsSchema } from './runtime_mappings_schema'; export const indexPatternTitleSchema = schema.object({ /** Title of the index pattern for which to return stats. */ indexPatternTitle: schema.string(), }); -const runtimeMappingsSchema = schema.maybe( - schema.object( - {}, - { - unknowns: 'allow', - validate: (v: object) => { - if (Object.values(v).some((o) => !isRuntimeField(o))) { - return 'Invalid runtime field'; - } - }, - } - ) -); - export const dataVisualizerFieldHistogramsSchema = schema.object({ /** Query to match documents in the index. */ query: schema.any(), diff --git a/x-pack/plugins/ml/server/routes/schemas/fields_service_schema.ts b/x-pack/plugins/ml/server/routes/schemas/fields_service_schema.ts index 76a307e710dc83..c598378926832a 100644 --- a/x-pack/plugins/ml/server/routes/schemas/fields_service_schema.ts +++ b/x-pack/plugins/ml/server/routes/schemas/fields_service_schema.ts @@ -7,6 +7,7 @@ import { schema } from '@kbn/config-schema'; import { indicesOptionsSchema } from './datafeeds_schema'; +import { runtimeMappingsSchema } from './runtime_mappings_schema'; export const getCardinalityOfFieldsSchema = schema.object({ /** Index or indexes for which to return the time range. */ @@ -31,6 +32,6 @@ export const getTimeFieldRangeSchema = schema.object({ /** Query to match documents in the index(es). */ query: schema.maybe(schema.any()), /** Additional search options. */ - runtimeMappings: schema.maybe(schema.any()), + runtimeMappings: runtimeMappingsSchema, indicesOptions: indicesOptionsSchema, }); diff --git a/x-pack/plugins/ml/server/routes/schemas/job_service_schema.ts b/x-pack/plugins/ml/server/routes/schemas/job_service_schema.ts index 8e160094c68eb2..fec64396321292 100644 --- a/x-pack/plugins/ml/server/routes/schemas/job_service_schema.ts +++ b/x-pack/plugins/ml/server/routes/schemas/job_service_schema.ts @@ -8,6 +8,7 @@ import { schema } from '@kbn/config-schema'; import { anomalyDetectionJobSchema } from './anomaly_detectors_schema'; import { datafeedConfigSchema, indicesOptionsSchema } from './datafeeds_schema'; +import { runtimeMappingsSchema } from './runtime_mappings_schema'; export const categorizationFieldExamplesSchema = { indexPatternTitle: schema.string(), @@ -18,7 +19,7 @@ export const categorizationFieldExamplesSchema = { start: schema.number(), end: schema.number(), analyzer: schema.any(), - runtimeMappings: schema.maybe(schema.any()), + runtimeMappings: runtimeMappingsSchema, indicesOptions: indicesOptionsSchema, }; @@ -32,7 +33,7 @@ export const chartSchema = { aggFieldNamePairs: schema.arrayOf(schema.any()), splitFieldName: schema.maybe(schema.nullable(schema.string())), splitFieldValue: schema.maybe(schema.nullable(schema.string())), - runtimeMappings: schema.maybe(schema.any()), + runtimeMappings: runtimeMappingsSchema, indicesOptions: indicesOptionsSchema, }; diff --git a/x-pack/plugins/ml/server/routes/schemas/job_validation_schema.ts b/x-pack/plugins/ml/server/routes/schemas/job_validation_schema.ts index ad2bafdfb5dd15..ddb6800e13fcde 100644 --- a/x-pack/plugins/ml/server/routes/schemas/job_validation_schema.ts +++ b/x-pack/plugins/ml/server/routes/schemas/job_validation_schema.ts @@ -8,6 +8,7 @@ import { schema } from '@kbn/config-schema'; import { analysisConfigSchema, anomalyDetectionJobSchema } from './anomaly_detectors_schema'; import { datafeedConfigSchema, indicesOptionsSchema } from './datafeeds_schema'; +import { runtimeMappingsSchema } from './runtime_mappings_schema'; export const estimateBucketSpanSchema = schema.object({ aggTypes: schema.arrayOf(schema.nullable(schema.string())), @@ -18,7 +19,7 @@ export const estimateBucketSpanSchema = schema.object({ query: schema.any(), splitField: schema.maybe(schema.string()), timeField: schema.maybe(schema.string()), - runtimeMappings: schema.maybe(schema.any()), + runtimeMappings: runtimeMappingsSchema, indicesOptions: indicesOptionsSchema, }); diff --git a/x-pack/plugins/ml/server/routes/schemas/runtime_mappings_schema.ts b/x-pack/plugins/ml/server/routes/schemas/runtime_mappings_schema.ts new file mode 100644 index 00000000000000..55247a79145c06 --- /dev/null +++ b/x-pack/plugins/ml/server/routes/schemas/runtime_mappings_schema.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { schema } from '@kbn/config-schema'; +import { isRuntimeField } from '../../../common/util/runtime_field_utils'; + +export const runtimeMappingsSchema = schema.maybe( + schema.object( + {}, + { + unknowns: 'allow', + validate: (v: object) => { + if (Object.values(v).some((o) => !isRuntimeField(o))) { + return 'Invalid runtime field'; + } + }, + } + ) +); From 6ee85e8835c3f7507165b4aa16501ad825ee1266 Mon Sep 17 00:00:00 2001 From: Kaarina Tungseth Date: Tue, 30 Mar 2021 15:33:45 -0500 Subject: [PATCH 26/32] [DOCS] Adds release-note:feature label (#91734) * [DOCS] Adds release-note:feature label * Update docs/developer/contributing/index.asciidoc Co-authored-by: Lisa Cawley * Update docs/developer/contributing/index.asciidoc Co-authored-by: Lisa Cawley * Update docs/developer/contributing/index.asciidoc Co-authored-by: Lisa Cawley Co-authored-by: Lisa Cawley --- docs/developer/contributing/index.asciidoc | 25 ++++++++++++---------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/docs/developer/contributing/index.asciidoc b/docs/developer/contributing/index.asciidoc index bbf2903491bf62..1cf96d19bfb2b2 100644 --- a/docs/developer/contributing/index.asciidoc +++ b/docs/developer/contributing/index.asciidoc @@ -1,7 +1,7 @@ [[contributing]] == Contributing -Whether you want to fix a bug, implement a feature, or add some other improvements or apis, the following sections will +Whether you want to fix a bug, implement a feature, add an improvement, or add APIs, the following sections will guide you on the process. After committing your code, check out the link:https://www.elastic.co/community/contributor[Elastic Contributor Program] where you can earn points and rewards for your contributions. Read <> to get your environment up and running, then read <>. @@ -53,24 +53,27 @@ To use a single paragraph of text, enter a `Release note:` or `## Release note` When you create the Release Notes text, use the following best practices: -* Use present tense. +* Use active voice. * Use sentence case. -* When you create a feature PR, start with `Adds`. -* When you create an enhancement PR, start with `Improves`. -* When you create a bug fix PR, start with `Fixes`. -* When you create a deprecation PR, start with `Deprecates`. +* When you create a PR that adds a feature, start with `Adds`. +* When you create a PR that improves an existing feature, start with `Improves`. +* When you create a PR that fixes existing functionality, start with `Fixes`. +* When you create a PR that deprecates functionality, start with `Deprecates`. [discrete] ==== Add your labels +To make sure that your PR is included in the Release Notes, add the right label. + [arabic] . Label the PR with the targeted version (ex: `v7.3.0`). . Label the PR with the appropriate GitHub labels: - * For a new feature or functionality, use `release_note:enhancement`. - * For an external-facing fix, use `release_note:fix`. We do not include docs, build, and test fixes in the Release Notes, or unreleased issues that are only on `master`. - * For a deprecated feature, use `release_note:deprecation`. - * For a breaking change, use `release_note:breaking`. - * To **NOT** include your changes in the Release Notes, use `release_note:skip`. + * `release_note:feature` — New user-facing features, significant enhancements to features, and significant bug fixes (in rare cases). + * `release_note:enhancement` — Minor UI changes and enhancements. + * `release_note:fix` — Fixes for bugs that existed in the previous release. + * `release_note:deprecation` — Deprecates functionality that existed in previous releases. + * `release_note:breaking` — Breaking changes that weren't present in previous releases. + * `release_note:skip` — Changes that should not appear in the Release Notes. For example, docs, build, and test fixes, or unreleased issues that are only in `master`. include::development-github.asciidoc[leveloffset=+1] From 80a19a86d7aad4e9256a08e91422a146170ba75f Mon Sep 17 00:00:00 2001 From: Jason Stoltzfus Date: Tue, 30 Mar 2021 17:07:16 -0400 Subject: [PATCH 27/32] [App Search] Result settings form (#95462) --- .../components/result_settings/constants.ts | 2 + .../result_settings/result_settings.test.tsx | 16 +- .../result_settings/result_settings.tsx | 29 +++- .../disabled_fields_body.test.tsx | 47 ++++++ .../disabled_fields_body.tsx | 40 +++++ .../disabled_fields_header.test.tsx | 19 +++ .../disabled_fields_header.tsx | 24 +++ .../field_number.test.tsx | 110 ++++++++++++++ .../result_settings_table/field_number.tsx | 91 +++++++++++ .../result_settings_table/index.ts | 8 + .../non_text_fields_body.test.tsx | 82 ++++++++++ .../non_text_fields_body.tsx | 55 +++++++ .../non_text_fields_header.test.tsx | 19 +++ .../non_text_fields_header.tsx | 31 ++++ .../result_settings_table.scss | 30 ++++ .../result_settings_table.test.tsx | 57 +++++++ .../result_settings_table.tsx | 51 +++++++ .../text_fields_body.test.tsx | 141 ++++++++++++++++++ .../text_fields_body.tsx | 105 +++++++++++++ .../text_fields_header.test.tsx | 19 +++ .../text_fields_header.tsx | 96 ++++++++++++ 21 files changed, 1064 insertions(+), 8 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/disabled_fields_body.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/disabled_fields_body.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/disabled_fields_header.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/disabled_fields_header.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/field_number.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/field_number.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/index.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/non_text_fields_body.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/non_text_fields_body.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/non_text_fields_header.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/non_text_fields_header.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/result_settings_table.scss create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/result_settings_table.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/result_settings_table.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/text_fields_body.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/text_fields_body.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/text_fields_header.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/text_fields_header.tsx diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/constants.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/constants.ts index 717cdc952fd679..2d0515146b0895 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/constants.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/constants.ts @@ -10,6 +10,8 @@ import { i18n } from '@kbn/i18n'; import { FieldResultSetting } from './types'; export const DEFAULT_SNIPPET_SIZE = 100; +export const SIZE_FIELD_MINIMUM = 20; +export const SIZE_FIELD_MAXIMUM = 1000; export const RESULT_SETTINGS_TITLE = i18n.translate( 'xpack.enterpriseSearch.appSearch.engine.resultSettings.title', diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings.test.tsx index 8ef70769273074..3388894c230a03 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings.test.tsx @@ -5,19 +5,33 @@ * 2.0. */ +import '../../../__mocks__/shallow_useeffect.mock'; + +import { setMockActions } from '../../../__mocks__'; + import React from 'react'; import { shallow } from 'enzyme'; import { ResultSettings } from './result_settings'; +import { ResultSettingsTable } from './result_settings_table'; describe('RelevanceTuning', () => { + const actions = { + initializeResultSettingsData: jest.fn(), + }; beforeEach(() => { + setMockActions(actions); jest.clearAllMocks(); }); it('renders', () => { const wrapper = shallow(); - expect(wrapper.isEmptyRender()).toBe(false); + expect(wrapper.find(ResultSettingsTable).exists()).toBe(true); + }); + + it('initializes result settings data when mounted', () => { + shallow(); + expect(actions.initializeResultSettingsData).toHaveBeenCalled(); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings.tsx index 6d6d5b26098988..38db5c60e98a9d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings.tsx @@ -5,29 +5,44 @@ * 2.0. */ -import React from 'react'; +import React, { useEffect } from 'react'; -import { EuiPageHeader, EuiPageContentBody, EuiPageContent } from '@elastic/eui'; +import { useActions } from 'kea'; + +import { EuiPageHeader, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { FlashMessages } from '../../../shared/flash_messages'; import { SetAppSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome'; import { RESULT_SETTINGS_TITLE } from './constants'; +import { ResultSettingsTable } from './result_settings_table'; + +import { ResultSettingsLogic } from '.'; interface Props { engineBreadcrumb: string[]; } export const ResultSettings: React.FC = ({ engineBreadcrumb }) => { + const { initializeResultSettingsData } = useActions(ResultSettingsLogic); + + useEffect(() => { + initializeResultSettingsData(); + }, []); + return ( <> - - - - - + + + + + + +
TODO
+
+
); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/disabled_fields_body.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/disabled_fields_body.test.tsx new file mode 100644 index 00000000000000..db87ac20a42239 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/disabled_fields_body.test.tsx @@ -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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { setMockValues } from '../../../../__mocks__'; + +import React from 'react'; + +import { shallow } from 'enzyme'; + +import { EuiTableRow } from '@elastic/eui'; + +import { DisabledFieldsBody } from './disabled_fields_body'; + +describe('DisabledFieldsBody', () => { + beforeEach(() => { + jest.clearAllMocks(); + setMockValues({ + schemaConflicts: { + foo: { + text: ['engine1'], + number: ['engine2'], + }, + bar: { + text: ['engine1'], + number: ['engine2'], + }, + }, + }); + }); + + it('renders a table row for each field', () => { + const wrapper = shallow(); + const tableRows = wrapper.find(EuiTableRow); + + expect(tableRows.length).toBe(2); + expect(tableRows.at(0).find('[data-test-subj="ResultSettingFieldName"]').dive().text()).toEqual( + 'foo' + ); + expect(tableRows.at(1).find('[data-test-subj="ResultSettingFieldName"]').dive().text()).toEqual( + 'bar' + ); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/disabled_fields_body.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/disabled_fields_body.tsx new file mode 100644 index 00000000000000..fd4646bf9a9f71 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/disabled_fields_body.tsx @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { useValues } from 'kea'; + +import { EuiTableBody, EuiTableRow, EuiTableRowCell, EuiText, EuiHealth } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import { ResultSettingsLogic } from '..'; + +export const DisabledFieldsBody: React.FC = () => { + const { schemaConflicts } = useValues(ResultSettingsLogic); + return ( + + {Object.keys(schemaConflicts).map((fieldName) => ( + + + + {fieldName} + + + {i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.resultSettings.fieldTypeConflictText', + { + defaultMessage: 'Field-type conflict', + } + )} + + + + ))} + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/disabled_fields_header.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/disabled_fields_header.test.tsx new file mode 100644 index 00000000000000..5237f9ae15c11d --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/disabled_fields_header.test.tsx @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { shallow } from 'enzyme'; + +import { DisabledFieldsHeader } from './disabled_fields_header'; + +describe('DisabledFieldsHeader', () => { + it('renders', () => { + const wrapper = shallow(); + expect(wrapper.isEmptyRender()).toBe(false); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/disabled_fields_header.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/disabled_fields_header.tsx new file mode 100644 index 00000000000000..0c82477814dabf --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/disabled_fields_header.tsx @@ -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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { EuiTableRow, EuiTableHeaderCell } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +export const DisabledFieldsHeader: React.FC = () => { + return ( + + + {i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.resultSettings.table.column.disabledFieldsTitle', + { defaultMessage: 'Disabled fields' } + )} + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/field_number.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/field_number.test.tsx new file mode 100644 index 00000000000000..3ac50d906e9c46 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/field_number.test.tsx @@ -0,0 +1,110 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { shallow } from 'enzyme'; + +import { EuiFieldNumber } from '@elastic/eui'; + +import { FieldResultSetting } from '../types'; + +import { FieldNumber } from './field_number'; + +describe('FieldNumber', () => { + const fieldSettings = { + raw: true, + rawSize: 29, + snippet: true, + snippetFallback: true, + snippetSize: 15, + }; + + const props = { + fieldSettings, + fieldName: 'foo', + fieldEnabledProperty: 'raw' as keyof FieldResultSetting, + fieldSizeProperty: 'rawSize' as keyof FieldResultSetting, + updateAction: jest.fn(), + clearAction: jest.fn(), + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('is rendered with its value set from [fieldSizeProperty] in fieldSettings', () => { + const wrapper = shallow(); + expect(wrapper.find(EuiFieldNumber).prop('value')).toEqual(29); + }); + + it('has no value if [fieldSizeProperty] in fieldSettings has no value', () => { + const wrapper = shallow( + + ); + expect(wrapper.find(EuiFieldNumber).prop('value')).toEqual(''); + }); + + it('is disabled if the [fieldEnabledProperty] in fieldSettings is false', () => { + const wrapper = shallow( + + ); + expect(wrapper.find(EuiFieldNumber).prop('disabled')).toEqual(true); + }); + + it('will call updateAction when the value is changed', () => { + const wrapper = shallow(); + wrapper.simulate('change', { target: { value: '21' } }); + expect(props.updateAction).toHaveBeenCalledWith('foo', 21); + }); + + it('will call clearAction when the value is changed', () => { + const wrapper = shallow(); + wrapper.simulate('change', { target: { value: '' } }); + expect(props.clearAction).toHaveBeenCalledWith('foo'); + }); + + it('will call updateAction on blur', () => { + const wrapper = shallow(); + wrapper.simulate('blur', { target: { value: '21' } }); + expect(props.updateAction).toHaveBeenCalledWith('foo', 21); + }); + + it('will call updateAction on blur using the minimum possible value if the current value is something other than a number', () => { + const wrapper = shallow(); + wrapper.simulate('blur', { target: { value: '' } }); + expect(props.updateAction).toHaveBeenCalledWith('foo', 20); + }); + + it('will call updateAction on blur using the minimum possible value if the value is something lower than the minimum', () => { + const wrapper = shallow(); + wrapper.simulate('blur', { target: { value: 5 } }); + expect(props.updateAction).toHaveBeenCalledWith('foo', 20); + }); + + it('will call updateAction on blur using the maximum possible value if the value is something above than the maximum', () => { + const wrapper = shallow(); + wrapper.simulate('blur', { target: { value: 2000 } }); + expect(props.updateAction).toHaveBeenCalledWith('foo', 1000); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/field_number.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/field_number.tsx new file mode 100644 index 00000000000000..cd7bab3c6f594a --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/field_number.tsx @@ -0,0 +1,91 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { ChangeEvent, FocusEvent } from 'react'; + +import { EuiFieldNumber } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import { SIZE_FIELD_MAXIMUM, SIZE_FIELD_MINIMUM } from '../constants'; +import { FieldResultSetting } from '../types'; + +const updateOrClearSizeForField = ( + fieldName: string, + fieldValue: number, + updateAction: (fieldName: string, size: number) => void, + clearAction: (fieldName: string) => void +) => { + if (typeof fieldValue === 'number' && !isNaN(fieldValue)) { + updateAction(fieldName, fieldValue); + } else { + clearAction(fieldName); + } +}; + +const handleFieldNumberChange = ( + fieldName: string, + updateAction: (fieldName: string, size: number) => void, + clearAction: (fieldName: string) => void +) => { + return (e: ChangeEvent) => { + const fieldValue = parseInt(e.target.value, 10); + updateOrClearSizeForField(fieldName, fieldValue, updateAction, clearAction); + }; +}; + +const handleFieldNumberBlur = ( + fieldName: string, + updateAction: (fieldName: string, size: number) => void, + clearAction: (fieldName: string) => void +) => { + return (e: FocusEvent) => { + const value = parseInt(e.target.value, 10); + const fieldValue = Math.min( + SIZE_FIELD_MAXIMUM, + Math.max(SIZE_FIELD_MINIMUM, isNaN(value) ? 0 : value) + ); + updateOrClearSizeForField(fieldName, fieldValue, updateAction, clearAction); + }; +}; + +interface Props { + fieldSettings: Partial; + fieldName: string; + fieldEnabledProperty: keyof FieldResultSetting; + fieldSizeProperty: keyof FieldResultSetting; + updateAction: (fieldName: string, size: number) => void; + clearAction: (fieldName: string) => void; +} + +export const FieldNumber: React.FC = ({ + fieldSettings, + fieldName, + fieldEnabledProperty, + fieldSizeProperty, + updateAction, + clearAction, +}) => { + return ( + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/index.ts new file mode 100644 index 00000000000000..1fcbf05bcc0c78 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { ResultSettingsTable } from './result_settings_table'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/non_text_fields_body.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/non_text_fields_body.test.tsx new file mode 100644 index 00000000000000..c99b8644812b94 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/non_text_fields_body.test.tsx @@ -0,0 +1,82 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { setMockValues, setMockActions } from '../../../../__mocks__/kea.mock'; + +import React from 'react'; + +import { shallow, ShallowWrapper } from 'enzyme'; + +import { EuiTableRow } from '@elastic/eui'; + +import { NonTextFieldsBody } from './non_text_fields_body'; + +describe('NonTextFieldsBody', () => { + const values = { + nonTextResultFields: { + foo: { + raw: false, + }, + zoo: { + raw: true, + rawSize: 5, + }, + bar: { + raw: true, + rawSize: 5, + }, + }, + }; + + const actions = { + toggleRawForField: jest.fn(), + }; + + beforeEach(() => { + jest.clearAllMocks(); + setMockValues(values); + setMockActions(actions); + }); + + const getTableRows = (wrapper: ShallowWrapper) => wrapper.find(EuiTableRow); + const getBarTableRow = (wrapper: ShallowWrapper) => getTableRows(wrapper).at(0); + + it('renders a table row for each field, sorted by field name', () => { + const wrapper = shallow(); + const tableRows = getTableRows(wrapper); + + expect(tableRows.length).toBe(3); + expect(tableRows.at(0).find('[data-test-subj="ResultSettingFieldName"]').dive().text()).toEqual( + 'bar' + ); + expect(tableRows.at(1).find('[data-test-subj="ResultSettingFieldName"]').dive().text()).toEqual( + 'foo' + ); + expect(tableRows.at(2).find('[data-test-subj="ResultSettingFieldName"]').dive().text()).toEqual( + 'zoo' + ); + }); + + describe('the "raw" checkbox within each table row', () => { + const getRawCheckbox = () => { + const wrapper = shallow(); + const tableRow = getBarTableRow(wrapper); + return tableRow.find('[data-test-subj="ResultSettingRawCheckBox"]'); + }; + + it('is rendered with its checked property set from state', () => { + const rawCheckbox = getRawCheckbox(); + expect(rawCheckbox.prop('checked')).toEqual(values.nonTextResultFields.bar.raw); + }); + + it("calls 'toggleRawForField' when it is clicked by a user", () => { + const rawCheckbox = getRawCheckbox(); + rawCheckbox.simulate('change'); + expect(actions.toggleRawForField).toHaveBeenCalledWith('bar'); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/non_text_fields_body.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/non_text_fields_body.tsx new file mode 100644 index 00000000000000..57dd2d5fdb974c --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/non_text_fields_body.tsx @@ -0,0 +1,55 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useMemo } from 'react'; + +import { useValues, useActions } from 'kea'; + +import { + EuiTableBody, + EuiTableRow, + EuiTableRowCell, + EuiCheckbox, + EuiTableRowCellCheckbox, +} from '@elastic/eui'; + +import { ResultSettingsLogic } from '..'; +import { FieldResultSetting } from '../types'; + +export const NonTextFieldsBody: React.FC = () => { + const { nonTextResultFields } = useValues(ResultSettingsLogic); + const { toggleRawForField } = useActions(ResultSettingsLogic); + + const resultSettingsArray: Array<[string, Partial]> = useMemo(() => { + return Object.entries(nonTextResultFields).sort(([aFieldName], [bFieldName]) => + aFieldName > bFieldName ? 1 : -1 + ); + }, [nonTextResultFields]); + + return ( + + {resultSettingsArray.map(([fieldName, fieldSettings]) => ( + + + {fieldName} + + + { + toggleRawForField(fieldName); + }} + /> + + + + ))} + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/non_text_fields_header.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/non_text_fields_header.test.tsx new file mode 100644 index 00000000000000..3c0920f29f8840 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/non_text_fields_header.test.tsx @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { shallow } from 'enzyme'; + +import { NonTextFieldsHeader } from './non_text_fields_header'; + +describe('NonTextFieldsHeader', () => { + it('renders', () => { + const wrapper = shallow(); + expect(wrapper.isEmptyRender()).toBe(false); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/non_text_fields_header.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/non_text_fields_header.tsx new file mode 100644 index 00000000000000..6024f736899deb --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/non_text_fields_header.tsx @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { EuiTableRow, EuiTableHeaderCell } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +export const NonTextFieldsHeader: React.FC = () => { + return ( + + + {i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.resultSettings.table.column.nonTextFieldsTitle', + { defaultMessage: 'Non-text fields' } + )} + + + {i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.resultSettings.table.column.rawTitle', + { defaultMessage: 'Raw' } + )} + + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/result_settings_table.scss b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/result_settings_table.scss new file mode 100644 index 00000000000000..7779abe7eb6fa5 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/result_settings_table.scss @@ -0,0 +1,30 @@ +.resultSettingsTable { + table-layout: auto; + + &__columnLabels { + .euiTableHeaderCell { + border-bottom: none; + padding-bottom: $euiSizeM; + } + } + + .euiTableRow:last-of-type { + .euiTableRowCell, .euiTableRowCellCheckbox { + // Because we are using the large border-top as a way of spacing, and table borders + // are collapsed, these tables do not have a bottom border. The exception to that is + // if the tbody is the last tbody of the table. To make it consistent, we explicitly + // disable all bottom borders here + border-bottom: none; + } + } + + tbody + &__subHeader { + // Since this table has multiple sets of thead + tbody's, this is our way of keeping + // vertical space between the two + border-top: solid $euiSizeL * 2 transparent; + } + + .euiTableRowCellCheckbox .euiTableCellContent { + justify-content: center; + } +} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/result_settings_table.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/result_settings_table.test.tsx new file mode 100644 index 00000000000000..151d436e59ea25 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/result_settings_table.test.tsx @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { setMockValues } from '../../../../__mocks__/kea.mock'; + +import React from 'react'; + +import { shallow } from 'enzyme'; + +import { DisabledFieldsHeader } from './disabled_fields_header'; +import { NonTextFieldsBody } from './non_text_fields_body'; +import { ResultSettingsTable } from './result_settings_table'; +import { TextFieldsBody } from './text_fields_body'; + +describe('ResultSettingsTable', () => { + beforeEach(() => { + jest.clearAllMocks(); + setMockValues({ + textResultFields: { foo: { raw: true, rawSize: 5, snippet: false, snippetFallback: false } }, + nonTextResultFields: { + bar: { raw: true, rawSize: 5, snippet: false, snippetFallback: false }, + }, + schemaConflicts: { + foo: { + text: ['foo'], + number: ['foo'], + geolocation: [], + date: [], + }, + }, + }); + }); + + it('renders', () => { + const wrapper = shallow(); + expect(wrapper.find(TextFieldsBody).exists()).toBe(true); + expect(wrapper.find(NonTextFieldsBody).exists()).toBe(true); + expect(wrapper.find(DisabledFieldsHeader).exists()).toBe(true); + }); + + it('will hide sections that have no data available to show', () => { + setMockValues({ + textResultFields: {}, + nonTextResultFields: {}, + schemaConflicts: {}, + }); + + const wrapper = shallow(); + expect(wrapper.find(TextFieldsBody).exists()).toBe(false); + expect(wrapper.find(NonTextFieldsBody).exists()).toBe(false); + expect(wrapper.find(DisabledFieldsHeader).exists()).toBe(false); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/result_settings_table.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/result_settings_table.tsx new file mode 100644 index 00000000000000..2da334e1f2ae2f --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/result_settings_table.tsx @@ -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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; + +import { useValues } from 'kea'; + +import { EuiTable } from '@elastic/eui'; + +import { ResultSettingsLogic } from '..'; + +import { DisabledFieldsBody } from './disabled_fields_body'; +import { DisabledFieldsHeader } from './disabled_fields_header'; +import { NonTextFieldsBody } from './non_text_fields_body'; +import { NonTextFieldsHeader } from './non_text_fields_header'; +import { TextFieldsBody } from './text_fields_body'; +import { TextFieldsHeader } from './text_fields_header'; + +import './result_settings_table.scss'; + +export const ResultSettingsTable: React.FC = () => { + const { schemaConflicts, textResultFields, nonTextResultFields } = useValues(ResultSettingsLogic); + + // TODO This table currently has mutiple theads, which is invalid html. We could change these subheaders to be EuiTableRow instead of EuiTableHeader + // to alleviate the issue. + return ( + + {!!Object.keys(textResultFields).length && ( + <> + + + + )} + {!!Object.keys(nonTextResultFields).length && ( + <> + + + + )} + {!!Object.keys(schemaConflicts).length && ( + <> + + + + )} + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/text_fields_body.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/text_fields_body.test.tsx new file mode 100644 index 00000000000000..7be58a387fa69f --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/text_fields_body.test.tsx @@ -0,0 +1,141 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { setMockValues, setMockActions } from '../../../../__mocks__/kea.mock'; + +import React from 'react'; + +import { shallow, ShallowWrapper } from 'enzyme'; + +import { EuiTableRow } from '@elastic/eui'; + +import { TextFieldsBody } from './text_fields_body'; + +describe('TextFieldsBody', () => { + const values = { + textResultFields: { + foo: { + raw: false, + snippet: true, + snippetFallback: true, + snippetSize: 15, + }, + zoo: { + raw: true, + rawSize: 5, + snippet: false, + snippetFallback: false, + }, + bar: { + raw: true, + rawSize: 5, + snippet: false, + snippetFallback: false, + }, + }, + }; + + const actions = { + toggleRawForField: jest.fn(), + updateRawSizeForField: jest.fn(), + clearRawSizeForField: jest.fn(), + toggleSnippetForField: jest.fn(), + updateSnippetSizeForField: jest.fn(), + clearSnippetSizeForField: jest.fn(), + toggleSnippetFallbackForField: jest.fn(), + }; + + beforeEach(() => { + jest.clearAllMocks(); + setMockValues(values); + setMockActions(actions); + }); + + const getTableRows = (wrapper: ShallowWrapper) => wrapper.find(EuiTableRow); + const getBarTableRow = (wrapper: ShallowWrapper) => getTableRows(wrapper).at(0); + const getFooTableRow = (wrapper: ShallowWrapper) => getTableRows(wrapper).at(1); + + it('renders a table row for each field, sorted by field name', () => { + const wrapper = shallow(); + const tableRows = getTableRows(wrapper); + + expect(tableRows.length).toBe(3); + expect(tableRows.at(0).find('[data-test-subj="ResultSettingFieldName"]').dive().text()).toEqual( + 'bar' + ); + expect(tableRows.at(1).find('[data-test-subj="ResultSettingFieldName"]').dive().text()).toEqual( + 'foo' + ); + expect(tableRows.at(2).find('[data-test-subj="ResultSettingFieldName"]').dive().text()).toEqual( + 'zoo' + ); + }); + + describe('the "raw" checkbox within each table row', () => { + const getRawCheckbox = () => { + const wrapper = shallow(); + const tableRow = getBarTableRow(wrapper); + return tableRow.find('[data-test-subj="ResultSettingRawCheckBox"]'); + }; + + it('is rendered with its checked property set from state', () => { + const rawCheckbox = getRawCheckbox(); + expect(rawCheckbox.prop('checked')).toEqual(values.textResultFields.bar.raw); + }); + + it("calls 'toggleRawForField' when it is clicked by a user", () => { + const rawCheckbox = getRawCheckbox(); + rawCheckbox.simulate('change'); + expect(actions.toggleRawForField).toHaveBeenCalledWith('bar'); + }); + }); + + describe('the "snippet" checkbox within each table row', () => { + const getSnippetCheckbox = () => { + const wrapper = shallow(); + const tableRow = getFooTableRow(wrapper); + return tableRow.find('[data-test-subj="ResultSettingSnippetTextBox"]'); + }; + + it('is rendered with its checked property set from state', () => { + const snippetCheckbox = getSnippetCheckbox(); + expect(snippetCheckbox.prop('checked')).toEqual(values.textResultFields.foo.snippet); + }); + + it("calls 'toggleRawForField' when it is clicked by a user", () => { + const snippetCheckbox = getSnippetCheckbox(); + snippetCheckbox.simulate('change'); + expect(actions.toggleSnippetForField).toHaveBeenCalledWith('foo'); + }); + }); + + describe('the "fallback" checkbox within each table row', () => { + const getFallbackCheckbox = () => { + const wrapper = shallow(); + const tableRow = getFooTableRow(wrapper); + return tableRow.find('[data-test-subj="ResultSettingFallbackTextBox"]'); + }; + + it('is rendered with its checked property set from state', () => { + const fallbackCheckbox = getFallbackCheckbox(); + expect(fallbackCheckbox.prop('checked')).toEqual(values.textResultFields.foo.snippetFallback); + }); + + it('is disabled if snippets are disabled for this field', () => { + const wrapper = shallow(); + const tableRow = getBarTableRow(wrapper); + const fallbackCheckbox = tableRow.find('[data-test-subj="ResultSettingFallbackTextBox"]'); + expect(fallbackCheckbox.prop('disabled')).toEqual(true); + }); + + it("calls 'toggleSnippetFallbackForField' when it is clicked by a user", () => { + const fallbackCheckbox = getFallbackCheckbox(); + fallbackCheckbox.simulate('change'); + expect(actions.toggleSnippetFallbackForField).toHaveBeenCalledWith('foo'); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/text_fields_body.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/text_fields_body.tsx new file mode 100644 index 00000000000000..af01ced81f7dd8 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/text_fields_body.tsx @@ -0,0 +1,105 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useMemo } from 'react'; + +import { useValues, useActions } from 'kea'; + +import { + EuiTableBody, + EuiTableRow, + EuiTableRowCell, + EuiTableRowCellCheckbox, + EuiCheckbox, +} from '@elastic/eui'; + +import { ResultSettingsLogic } from '../result_settings_logic'; +import { FieldResultSetting } from '../types'; + +import { FieldNumber } from './field_number'; + +export const TextFieldsBody: React.FC = () => { + const { textResultFields } = useValues(ResultSettingsLogic); + const { + toggleRawForField, + updateRawSizeForField, + clearRawSizeForField, + toggleSnippetForField, + updateSnippetSizeForField, + clearSnippetSizeForField, + toggleSnippetFallbackForField, + } = useActions(ResultSettingsLogic); + + const resultSettingsArray: Array<[string, Partial]> = useMemo(() => { + return Object.entries(textResultFields).sort(([aFieldName], [bFieldName]) => + aFieldName > bFieldName ? 1 : -1 + ); + }, [textResultFields]); + + return ( + + {resultSettingsArray.map(([fieldName, fieldSettings]) => ( + + + {fieldName} + + + { + toggleRawForField(fieldName); + }} + /> + + + + + + { + toggleSnippetForField(fieldName); + }} + /> + + + { + toggleSnippetFallbackForField(fieldName); + }} + /> + + + + + + ))} + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/text_fields_header.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/text_fields_header.test.tsx new file mode 100644 index 00000000000000..be7d18e375fddc --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/text_fields_header.test.tsx @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { shallow } from 'enzyme'; + +import { TextFieldsHeader } from './text_fields_header'; + +describe('TextFieldsHeader', () => { + it('renders', () => { + const wrapper = shallow(); + expect(wrapper.isEmptyRender()).toBe(false); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/text_fields_header.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/text_fields_header.tsx new file mode 100644 index 00000000000000..3810570b3e3a29 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/text_fields_header.tsx @@ -0,0 +1,96 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { EuiTableRow, EuiTableHeader, EuiTableHeaderCell, EuiIconTip } from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +export const TextFieldsHeader: React.FC = () => { + return ( + <> + + + + {i18n.translate('xpack.enterpriseSearch.appSearch.engine.resultSettings.table.rawTitle', { + defaultMessage: 'Raw', + })} + + + + {i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.resultSettings.table.highlightingTitle', + { + defaultMessage: 'Highlighting', + } + )} + tags for highlighting. Fallback will look for a snippet match, but fallback to an escaped raw value if none is found. Range is between 20-1000. Defaults to 100.', + } + )} + /> + + + + + {i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.resultSettings.table.column.textFieldsTitle', + { defaultMessage: 'Text fields' } + )} + + {/* TODO Right now the stacked "Raw" ths leads screen readers to reading out Raw - Raw - Raw 3x in a row once you get down to the non-text fields. We should consider either: + Channging this "Raw" column to something like "Enabled" + Or losing the RAW vs HIGHLIGHTING top-level headings */} + + {i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.resultSettings.table.column.rawTitle', + { defaultMessage: 'Raw' } + )} + + + {i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.resultSettings.table.column.maxSizeTitle', + { defaultMessage: 'Max size' } + )} + + + {i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.resultSettings.table.column.snippetTitle', + { defaultMessage: 'Snippet' } + )} + + + {i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.resultSettings.table.column.fallbackTitle', + { defaultMessage: 'Fallback' } + )} + + + {i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.resultSettings.table.column.maxSizeTitle', + { defaultMessage: 'Max size' } + )} + + + + ); +}; From cd4483f39111e75e1addf21a1165d544cad0a9c5 Mon Sep 17 00:00:00 2001 From: Quynh Nguyen <43350163+qn895@users.noreply.github.com> Date: Tue, 30 Mar 2021 16:58:31 -0500 Subject: [PATCH 28/32] [ML] Fix runtime type import from data/common (#95841) --- x-pack/plugins/ml/common/util/runtime_field_utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/ml/common/util/runtime_field_utils.ts b/x-pack/plugins/ml/common/util/runtime_field_utils.ts index 06a1960cc943bb..f2df5821268cd3 100644 --- a/x-pack/plugins/ml/common/util/runtime_field_utils.ts +++ b/x-pack/plugins/ml/common/util/runtime_field_utils.ts @@ -6,7 +6,7 @@ */ import { isPopulatedObject } from './object_utils'; -import { RUNTIME_FIELD_TYPES } from '../../../../../src/plugins/data/common/index_patterns'; +import { RUNTIME_FIELD_TYPES } from '../../../../../src/plugins/data/common'; import type { RuntimeField, RuntimeMappings } from '../types/fields'; export function isRuntimeField(arg: unknown): arg is RuntimeField { From 2010f4eba18a874990e5631fbc0b2ad9c71e34dd Mon Sep 17 00:00:00 2001 From: Spencer Date: Tue, 30 Mar 2021 15:08:12 -0700 Subject: [PATCH 29/32] [cli-dev-mode/optimizer] omit pageLoadAssetSizeLimit from cache (#95826) Co-authored-by: spalger --- .../kbn-cli-dev-mode/src/cli_dev_mode.test.ts | 1 + packages/kbn-cli-dev-mode/src/cli_dev_mode.ts | 2 ++ packages/kbn-cli-dev-mode/src/optimizer.test.ts | 1 + packages/kbn-cli-dev-mode/src/optimizer.ts | 2 ++ packages/kbn-config/src/env.ts | 1 + .../kbn-optimizer/src/common/bundle.test.ts | 1 - packages/kbn-optimizer/src/common/bundle.ts | 3 ++- packages/kbn-optimizer/src/common/index.ts | 1 + .../kbn-optimizer/src/common/obj_helpers.ts | 17 +++++++++++++++++ .../src/optimizer/optimizer_config.ts | 11 +---------- src/cli/serve/serve.js | 1 + 11 files changed, 29 insertions(+), 12 deletions(-) create mode 100644 packages/kbn-optimizer/src/common/obj_helpers.ts diff --git a/packages/kbn-cli-dev-mode/src/cli_dev_mode.test.ts b/packages/kbn-cli-dev-mode/src/cli_dev_mode.test.ts index d5bafe7280bd92..7b45a2639c668c 100644 --- a/packages/kbn-cli-dev-mode/src/cli_dev_mode.test.ts +++ b/packages/kbn-cli-dev-mode/src/cli_dev_mode.test.ts @@ -135,6 +135,7 @@ it('passes correct args to sub-classes', () => { "repoRoot": , "runExamples": false, "silent": false, + "verbose": false, "watch": true, }, ], diff --git a/packages/kbn-cli-dev-mode/src/cli_dev_mode.ts b/packages/kbn-cli-dev-mode/src/cli_dev_mode.ts index 94dbcb9654e8ae..7e730d2cb70204 100644 --- a/packages/kbn-cli-dev-mode/src/cli_dev_mode.ts +++ b/packages/kbn-cli-dev-mode/src/cli_dev_mode.ts @@ -37,6 +37,7 @@ export type SomeCliArgs = Pick< CliArgs, | 'quiet' | 'silent' + | 'verbose' | 'disableOptimizer' | 'watch' | 'oss' @@ -148,6 +149,7 @@ export class CliDevMode { dist: cliArgs.dist, quiet: !!cliArgs.quiet, silent: !!cliArgs.silent, + verbose: !!cliArgs.verbose, watch: cliArgs.watch, }); } diff --git a/packages/kbn-cli-dev-mode/src/optimizer.test.ts b/packages/kbn-cli-dev-mode/src/optimizer.test.ts index c270a00329897a..e3bfb2eb0bb9e9 100644 --- a/packages/kbn-cli-dev-mode/src/optimizer.test.ts +++ b/packages/kbn-cli-dev-mode/src/optimizer.test.ts @@ -46,6 +46,7 @@ const defaultOptions: Options = { pluginScanDirs: ['/some-scan-path'], quiet: true, silent: true, + verbose: false, repoRoot: '/app', runExamples: true, watch: true, diff --git a/packages/kbn-cli-dev-mode/src/optimizer.ts b/packages/kbn-cli-dev-mode/src/optimizer.ts index 5e2f16fcf7daa8..750b61140e920a 100644 --- a/packages/kbn-cli-dev-mode/src/optimizer.ts +++ b/packages/kbn-cli-dev-mode/src/optimizer.ts @@ -25,6 +25,7 @@ export interface Options { repoRoot: string; quiet: boolean; silent: boolean; + verbose: boolean; watch: boolean; cache: boolean; dist: boolean; @@ -80,6 +81,7 @@ export class Optimizer { const { flags: levelFlags } = parseLogLevel( pickLevelFromFlags({ + verbose: options.verbose, quiet: options.quiet, silent: options.silent, }) diff --git a/packages/kbn-config/src/env.ts b/packages/kbn-config/src/env.ts index c4845ab429c573..053bb93ce158c3 100644 --- a/packages/kbn-config/src/env.ts +++ b/packages/kbn-config/src/env.ts @@ -24,6 +24,7 @@ export interface CliArgs { /** @deprecated */ quiet?: boolean; silent?: boolean; + verbose?: boolean; watch: boolean; basePath: boolean; oss: boolean; diff --git a/packages/kbn-optimizer/src/common/bundle.test.ts b/packages/kbn-optimizer/src/common/bundle.test.ts index ff9aa6fd906280..9dbaae9f36f200 100644 --- a/packages/kbn-optimizer/src/common/bundle.test.ts +++ b/packages/kbn-optimizer/src/common/bundle.test.ts @@ -42,7 +42,6 @@ it('creates cache keys', () => { "id": "bar", "manifestPath": undefined, "outputDir": "/foo/bar/target", - "pageLoadAssetSizeLimit": undefined, "publicDirNames": Array [ "public", ], diff --git a/packages/kbn-optimizer/src/common/bundle.ts b/packages/kbn-optimizer/src/common/bundle.ts index 64b44de0dd1b3e..08946deec0b47d 100644 --- a/packages/kbn-optimizer/src/common/bundle.ts +++ b/packages/kbn-optimizer/src/common/bundle.ts @@ -11,6 +11,7 @@ import Fs from 'fs'; import { BundleCache } from './bundle_cache'; import { UnknownVals } from './ts_helpers'; +import { omit } from './obj_helpers'; import { includes, ascending, entriesToObject } from './array_helpers'; const VALID_BUNDLE_TYPES = ['plugin' as const, 'entry' as const]; @@ -90,7 +91,7 @@ export class Bundle { */ createCacheKey(files: string[], mtimes: Map): unknown { return { - spec: this.toSpec(), + spec: omit(this.toSpec(), ['pageLoadAssetSizeLimit']), mtimes: entriesToObject( files.map((p) => [p, mtimes.get(p)] as const).sort(ascending((e) => e[0])) ), diff --git a/packages/kbn-optimizer/src/common/index.ts b/packages/kbn-optimizer/src/common/index.ts index 0f2c6a35176030..7914d74fa92990 100644 --- a/packages/kbn-optimizer/src/common/index.ts +++ b/packages/kbn-optimizer/src/common/index.ts @@ -18,3 +18,4 @@ export * from './array_helpers'; export * from './event_stream_helpers'; export * from './parse_path'; export * from './theme_tags'; +export * from './obj_helpers'; diff --git a/packages/kbn-optimizer/src/common/obj_helpers.ts b/packages/kbn-optimizer/src/common/obj_helpers.ts new file mode 100644 index 00000000000000..f238eb22e93a11 --- /dev/null +++ b/packages/kbn-optimizer/src/common/obj_helpers.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 + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export function omit(obj: T, keys: K[]): Omit { + const result: any = {}; + for (const [key, value] of Object.entries(obj) as any) { + if (!keys.includes(key)) { + result[key] = value; + } + } + return result as Omit; +} diff --git a/packages/kbn-optimizer/src/optimizer/optimizer_config.ts b/packages/kbn-optimizer/src/optimizer/optimizer_config.ts index 9110b6db27e927..2dbe48c15483fc 100644 --- a/packages/kbn-optimizer/src/optimizer/optimizer_config.ts +++ b/packages/kbn-optimizer/src/optimizer/optimizer_config.ts @@ -17,6 +17,7 @@ import { ThemeTag, ThemeTags, parseThemeTags, + omit, } from '../common'; import { findKibanaPlatformPlugins, KibanaPlatformPlugin } from './kibana_platform_plugins'; @@ -40,16 +41,6 @@ function pickMaxWorkerCount(dist: boolean) { return Math.max(maxWorkers, 2); } -function omit(obj: T, keys: K[]): Omit { - const result: any = {}; - for (const [key, value] of Object.entries(obj) as any) { - if (!keys.includes(key)) { - result[key] = value; - } - } - return result as Omit; -} - interface Options { /** absolute path to root of the repo/build */ repoRoot: string; diff --git a/src/cli/serve/serve.js b/src/cli/serve/serve.js index 86b4ac53841f71..a494e4538e79a7 100644 --- a/src/cli/serve/serve.js +++ b/src/cli/serve/serve.js @@ -228,6 +228,7 @@ export default function (program) { // no longer supported quiet: !!opts.quiet, silent: !!opts.silent, + verbose: !!opts.verbose, watch: !!opts.watch, runExamples: !!opts.runExamples, // We want to run without base path when the `--run-examples` flag is given so that we can use local From 46c93b16bb5493f5bcb317636952728eea06afa2 Mon Sep 17 00:00:00 2001 From: Tyler Smalley Date: Tue, 30 Mar 2021 15:51:23 -0700 Subject: [PATCH 30/32] [Github] Add automation for needs-team label (#95728) Signed-off-by: Tyler Smalley --- .github/relabel.yml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .github/relabel.yml diff --git a/.github/relabel.yml b/.github/relabel.yml new file mode 100644 index 00000000000000..a737be356ce81a --- /dev/null +++ b/.github/relabel.yml @@ -0,0 +1,3 @@ +issues: + - missingLabel: needs-team + regex: ^(\:ml)|(Team:.*)$ \ No newline at end of file From c35ef75dbb8e73654297ebbac00f82db8751e024 Mon Sep 17 00:00:00 2001 From: Spencer Date: Tue, 30 Mar 2021 15:57:10 -0700 Subject: [PATCH 31/32] [cli-dev-mode] complete shutdown once devServer stops gracefully (#95822) Co-authored-by: spalger Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../src/base_path_proxy_server.ts | 3 -- packages/kbn-cli-dev-mode/src/cli_dev_mode.ts | 46 +++++++++++++++++-- 2 files changed, 43 insertions(+), 6 deletions(-) diff --git a/packages/kbn-cli-dev-mode/src/base_path_proxy_server.ts b/packages/kbn-cli-dev-mode/src/base_path_proxy_server.ts index 40841c8327cc29..6d12d5d05f07c7 100644 --- a/packages/kbn-cli-dev-mode/src/base_path_proxy_server.ts +++ b/packages/kbn-cli-dev-mode/src/base_path_proxy_server.ts @@ -63,8 +63,6 @@ export class BasePathProxyServer { } public async start(options: BasePathProxyServerOptions) { - this.log.write('starting basepath proxy server'); - const serverOptions = getServerOptions(this.httpConfig); const listenerOptions = getListenerOptions(this.httpConfig); this.server = createServer(serverOptions, listenerOptions); @@ -101,7 +99,6 @@ export class BasePathProxyServer { return; } - this.log.write('stopping basepath proxy server'); await this.server.stop(); this.server = undefined; diff --git a/packages/kbn-cli-dev-mode/src/cli_dev_mode.ts b/packages/kbn-cli-dev-mode/src/cli_dev_mode.ts index 7e730d2cb70204..e867a7276989c2 100644 --- a/packages/kbn-cli-dev-mode/src/cli_dev_mode.ts +++ b/packages/kbn-cli-dev-mode/src/cli_dev_mode.ts @@ -7,6 +7,8 @@ */ import Path from 'path'; +import { EventEmitter } from 'events'; + import * as Rx from 'rxjs'; import { map, @@ -17,6 +19,7 @@ import { distinctUntilChanged, switchMap, concatMap, + takeUntil, } from 'rxjs/operators'; import { CliArgs } from '@kbn/config'; import { REPO_ROOT, CiStatsReporter } from '@kbn/dev-utils'; @@ -30,6 +33,16 @@ import { shouldRedirectFromOldBasePath } from './should_redirect_from_old_base_p import { getServerWatchPaths } from './get_server_watch_paths'; import { CliDevConfig } from './config'; +// signal that emits undefined once a termination signal has been sent +const exitSignal$ = new Rx.ReplaySubject(1); +Rx.merge( + Rx.fromEvent(process as EventEmitter, 'exit'), + Rx.fromEvent(process as EventEmitter, 'SIGINT'), + Rx.fromEvent(process as EventEmitter, 'SIGTERM') +) + .pipe(mapTo(undefined), take(1)) + .subscribe(exitSignal$); + // timeout where the server is allowed to exit gracefully const GRACEFUL_TIMEOUT = 5000; @@ -218,9 +231,36 @@ export class CliDevMode { this.log.warn('no-base-path', '='.repeat(100)); } - this.subscription.add(this.optimizer.run$.subscribe(this.observer('@kbn/optimizer'))); - this.subscription.add(this.watcher.run$.subscribe(this.observer('watcher'))); - this.subscription.add(this.devServer.run$.subscribe(this.observer('dev server'))); + this.subscription.add( + this.optimizer.run$ + .pipe( + // stop the optimizer as soon as we get an exit signal + takeUntil(exitSignal$) + ) + .subscribe(this.observer('@kbn/optimizer')) + ); + + this.subscription.add( + this.watcher.run$ + .pipe( + // stop the watcher as soon as we get an exit signal + takeUntil(exitSignal$) + ) + .subscribe(this.observer('watcher')) + ); + + this.subscription.add( + this.devServer.run$ + .pipe( + tap({ + complete: () => { + // when the devServer gracefully exits because of an exit signal stop the cli dev mode to trigger full shutdown + this.stop(); + }, + }) + ) + .subscribe(this.observer('dev server')) + ); } private reportTimings(reporter: CiStatsReporter) { From 50313f75f69cf6410ccdd3ad634a866909d8a051 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Wed, 31 Mar 2021 00:02:22 +0100 Subject: [PATCH 32/32] chore(NA): enable preserve symlinks for ts without breaking packages development (#95433) * chore(NA): move elastic-datemath into a ts package * chore(NA): finish elastic-datemath * chore(NA): finish elastic-datemath * chore(NA): source folder for elastic-datemath * chore(NA): add source-maps ace, analytics, apm-config-loader and apm-utils packages * chore(NA): add sourcemaps to packages on typescript * chore(NA): move test fixtures within source * chore(NA): correct exclusions on packages * chore(NA): correct package.json on all packages * chore(NA): correct package.json on all packages * chore(NA): complete kbn pm * chore(NA): default export on elastic-datemath * chore(NA): include logs on kbn-logging * chore(NA): update bundle ref module to last code used in the webpack upstream * chore(NA): update bundle ref module to last code used in the webpack upstream - refactored * chore(NA): remove override method for exportsArgument * fix(NA): typechecking problems by use @internal at javascript import sources on kbn-test package * fix(NA): typescript projects check * fix(NA): run optimizer integration tests from source * chore(NA): fix usage from target for kbn optimizer * chore(NA): path on tsconfig * chore(NA): move tsignore into ts-expect-error * chore(NA): include souce maps on kbn cli dev * chore(NA): include souce maps on kbn-crypto, kbn-server-http-tools and kbn-telemetry-tools * chore(NA): add issue links into the ts-expect-error comments --- .../elastic-datemath/{readme => README.md} | 0 packages/elastic-datemath/index.d.ts | 51 ---------------- packages/elastic-datemath/package.json | 8 ++- .../elastic-datemath/{ => src}/index.test.js | 0 .../{index.js => src/index.ts} | 59 ++++++++++++------- packages/elastic-datemath/tsconfig.json | 12 +++- packages/kbn-ace/package.json | 1 + packages/kbn-ace/tsconfig.json | 6 +- packages/kbn-analytics/tsconfig.json | 11 ++-- .../{ => src}/__fixtures__/config.yml | 0 .../{ => src}/__fixtures__/config_flat.yml | 0 .../__fixtures__/en_var_ref_config.yml | 0 .../{ => src}/__fixtures__/one.yml | 0 .../{ => src}/__fixtures__/two.yml | 0 .../src/utils/read_config.test.ts | 2 +- packages/kbn-apm-config-loader/tsconfig.json | 15 +++-- packages/kbn-apm-utils/tsconfig.json | 8 +-- packages/kbn-cli-dev-mode/tsconfig.json | 15 +++-- packages/kbn-config-schema/tsconfig.json | 14 ++--- .../{ => src}/__fixtures__/config.yml | 0 .../{ => src}/__fixtures__/config_flat.yml | 0 .../__fixtures__/en_var_ref_config.yml | 0 .../kbn-config/{ => src}/__fixtures__/one.yml | 0 .../kbn-config/{ => src}/__fixtures__/two.yml | 0 .../kbn-config/src/raw/read_config.test.ts | 2 +- packages/kbn-config/tsconfig.json | 18 ++++-- packages/kbn-crypto/package.json | 1 + packages/kbn-crypto/tsconfig.json | 7 ++- packages/kbn-dev-utils/package.json | 1 + packages/kbn-dev-utils/tsconfig.json | 10 +++- packages/kbn-docs-utils/package.json | 1 + packages/kbn-docs-utils/tsconfig.json | 9 ++- packages/kbn-es-archiver/package.json | 1 + packages/kbn-es-archiver/tsconfig.json | 9 ++- packages/kbn-i18n/tsconfig.json | 26 ++++---- packages/kbn-interpreter/common/package.json | 1 + packages/kbn-legacy-logging/package.json | 1 + packages/kbn-legacy-logging/tsconfig.json | 12 +++- packages/kbn-logging/package.json | 1 + packages/kbn-logging/tsconfig.json | 12 +++- packages/kbn-monaco/package.json | 1 + packages/kbn-monaco/tsconfig.json | 3 + packages/kbn-optimizer/package.json | 3 +- .../basic_optimization.test.ts | 8 +-- .../src/optimizer/observe_worker.ts | 32 ++++++---- .../src/worker/bundle_ref_module.ts | 5 +- .../worker/run_worker_from_source.js} | 3 +- packages/kbn-optimizer/tsconfig.json | 11 +++- packages/kbn-plugin-generator/package.json | 1 + packages/kbn-plugin-generator/tsconfig.json | 16 ++++- packages/kbn-plugin-helpers/package.json | 1 + packages/kbn-plugin-helpers/tsconfig.json | 9 ++- packages/kbn-pm/dist/index.js | 20 +++---- packages/kbn-pm/tsconfig.json | 12 ++-- packages/kbn-server-http-tools/package.json | 1 + packages/kbn-server-http-tools/tsconfig.json | 12 ++-- packages/kbn-std/tsconfig.json | 18 ++++-- packages/kbn-storybook/package.json | 1 + packages/kbn-storybook/tsconfig.json | 16 ++++- packages/kbn-telemetry-tools/package.json | 1 + packages/kbn-telemetry-tools/tsconfig.json | 7 ++- packages/kbn-test/index.d.ts | 9 --- packages/kbn-test/package.json | 1 + ...e_mocha_types.d.ts => fake_mocha_types.ts} | 0 .../src/functional_test_runner/lib/index.ts | 1 + .../functional_test_runner/lib/mocha/index.ts | 2 + packages/kbn-test/src/index.ts | 8 +++ packages/kbn-test/src/kbn_archiver_cli.ts | 2 +- packages/kbn-test/src/mocha/index.ts | 3 + packages/kbn-test/tsconfig.json | 30 ++++++---- packages/kbn-utility-types/package.json | 2 +- packages/kbn-utility-types/tsconfig.json | 18 +++--- packages/kbn-utils/package.json | 1 + packages/kbn-utils/tsconfig.json | 9 ++- src/core/test_helpers/kbn_server.ts | 7 +++ tsconfig.base.json | 2 + x-pack/test/examples/config.ts | 1 + .../functional/page_objects/security_page.ts | 1 + x-pack/test/functional_cors/config.ts | 1 + .../plugins/kibana_cors_test/server/plugin.ts | 1 + x-pack/test/licensing_plugin/config.public.ts | 1 + x-pack/test/plugin_functional/config.ts | 1 + .../reporting_and_security.config.ts | 1 + .../reporting_without_security.config.ts | 1 + .../tests/anonymous/login.ts | 1 + .../tests/kerberos/kerberos_login.ts | 1 + .../oidc/authorization_code_flow/oidc_auth.ts | 1 + .../tests/pki/pki_auth.ts | 1 + .../tests/saml/saml_login.ts | 1 + .../tests/session_idle/cleanup.ts | 1 + .../tests/session_invalidate/invalidate.ts | 1 + .../tests/session_lifespan/cleanup.ts | 1 + .../services/endpoint_telemetry.ts | 1 + 93 files changed, 389 insertions(+), 218 deletions(-) rename packages/elastic-datemath/{readme => README.md} (100%) delete mode 100644 packages/elastic-datemath/index.d.ts rename packages/elastic-datemath/{ => src}/index.test.js (100%) rename packages/elastic-datemath/{index.js => src/index.ts} (71%) rename packages/kbn-apm-config-loader/{ => src}/__fixtures__/config.yml (100%) rename packages/kbn-apm-config-loader/{ => src}/__fixtures__/config_flat.yml (100%) rename packages/kbn-apm-config-loader/{ => src}/__fixtures__/en_var_ref_config.yml (100%) rename packages/kbn-apm-config-loader/{ => src}/__fixtures__/one.yml (100%) rename packages/kbn-apm-config-loader/{ => src}/__fixtures__/two.yml (100%) rename packages/kbn-config/{ => src}/__fixtures__/config.yml (100%) rename packages/kbn-config/{ => src}/__fixtures__/config_flat.yml (100%) rename packages/kbn-config/{ => src}/__fixtures__/en_var_ref_config.yml (100%) rename packages/kbn-config/{ => src}/__fixtures__/one.yml (100%) rename packages/kbn-config/{ => src}/__fixtures__/two.yml (100%) rename packages/kbn-optimizer/{index.d.ts => src/worker/run_worker_from_source.js} (80%) delete mode 100644 packages/kbn-test/index.d.ts rename packages/kbn-test/src/functional_test_runner/{fake_mocha_types.d.ts => fake_mocha_types.ts} (100%) diff --git a/packages/elastic-datemath/readme b/packages/elastic-datemath/README.md similarity index 100% rename from packages/elastic-datemath/readme rename to packages/elastic-datemath/README.md diff --git a/packages/elastic-datemath/index.d.ts b/packages/elastic-datemath/index.d.ts deleted file mode 100644 index 319c598e3e4ab0..00000000000000 --- a/packages/elastic-datemath/index.d.ts +++ /dev/null @@ -1,51 +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 moment from 'moment'; -export type Unit = 'ms' | 's' | 'm' | 'h' | 'd' | 'w' | 'M' | 'y'; - -declare const datemath: { - unitsMap: { - [k in Unit]: { - weight: number; - type: 'calendar' | 'fixed' | 'mixed'; - base: number; - }; - }; - units: Unit[]; - unitsAsc: Unit[]; - unitsDesc: Unit[]; - - /** - * Parses a string into a moment object. The string can be something like "now - 15m". - * @param options.forceNow If this optional parameter is supplied, "now" will be treated as this - * date, rather than the real "now". - */ - parse( - input: string, - options?: { - roundUp?: boolean; - forceNow?: Date; - momentInstance?: typeof moment; - } - ): moment.Moment | undefined; -}; - -// eslint-disable-next-line import/no-default-export -export default datemath; diff --git a/packages/elastic-datemath/package.json b/packages/elastic-datemath/package.json index 0d8f936ae6358b..4dc9c4f24d5678 100644 --- a/packages/elastic-datemath/package.json +++ b/packages/elastic-datemath/package.json @@ -3,6 +3,10 @@ "version": "5.0.3", "description": "elasticsearch datemath parser, used in kibana", "license": "Apache-2.0", - "main": "index.js", - "typings": "index.d.ts" + "main": "./target/index.js", + "types": "./target/index.d.ts", + "scripts": { + "build": "../../node_modules/.bin/tsc", + "kbn:bootstrap": "yarn build" + } } \ No newline at end of file diff --git a/packages/elastic-datemath/index.test.js b/packages/elastic-datemath/src/index.test.js similarity index 100% rename from packages/elastic-datemath/index.test.js rename to packages/elastic-datemath/src/index.test.js diff --git a/packages/elastic-datemath/index.js b/packages/elastic-datemath/src/index.ts similarity index 71% rename from packages/elastic-datemath/index.js rename to packages/elastic-datemath/src/index.ts index 8a69d251d057dd..a513af800b7c36 100644 --- a/packages/elastic-datemath/index.js +++ b/packages/elastic-datemath/src/index.ts @@ -17,9 +17,18 @@ * under the License. */ -const moment = require('moment'); +import moment from 'moment'; + +export type Unit = 'ms' | 's' | 'm' | 'h' | 'd' | 'w' | 'M' | 'y'; +export type UnitsMap = { + [k in Unit]: { + weight: number; + type: 'calendar' | 'fixed' | 'mixed'; + base: number; + }; +}; -const unitsMap = { +export const unitsMap: UnitsMap = { ms: { weight: 1, type: 'fixed', base: 1 }, s: { weight: 2, type: 'fixed', base: 1000 }, m: { weight: 3, type: 'mixed', base: 1000 * 60 }, @@ -30,13 +39,14 @@ const unitsMap = { // q: { weight: 8, type: 'calendar' }, // TODO: moment duration does not support quarter y: { weight: 9, type: 'calendar', base: NaN }, }; -const units = Object.keys(unitsMap).sort((a, b) => unitsMap[b].weight - unitsMap[a].weight); -const unitsDesc = [...units]; -const unitsAsc = [...units].reverse(); - -const isDate = (d) => Object.prototype.toString.call(d) === '[object Date]'; +export const units: Unit[] = Object.keys(unitsMap).sort( + (a, b) => unitsMap[b as Unit].weight - unitsMap[a as Unit].weight +) as Unit[]; +export const unitsDesc: Unit[] = [...units] as Unit[]; +export const unitsAsc: Unit[] = [...units].reverse() as Unit[]; -const isValidDate = (d) => isDate(d) && !isNaN(d.valueOf()); +const isDate = (d: string) => Object.prototype.toString.call(d) === '[object Date]'; +const isValidDate = (d: string) => isDate(d) && !isNaN(d.valueOf() as any); /* * This is a simplified version of elasticsearch's date parser. @@ -44,11 +54,17 @@ const isValidDate = (d) => isDate(d) && !isNaN(d.valueOf()); * will be done using this (and its locale settings) instead of the one bundled * with this library. */ -function parse(text, { roundUp = false, momentInstance = moment, forceNow } = {}) { +export function parse( + input: string, + options: { roundUp?: boolean; momentInstance?: typeof moment; forceNow?: Date } = {} +) { + const text = input; + const { roundUp = false, momentInstance = moment, forceNow } = options; + if (!text) return undefined; if (momentInstance.isMoment(text)) return text; if (isDate(text)) return momentInstance(text); - if (forceNow !== undefined && !isValidDate(forceNow)) { + if (forceNow !== undefined && !isValidDate(forceNow as any)) { throw new Error('forceNow must be a valid Date'); } @@ -80,7 +96,7 @@ function parse(text, { roundUp = false, momentInstance = moment, forceNow } = {} return parseDateMath(mathString, time, roundUp); } -function parseDateMath(mathString, time, roundUp) { +function parseDateMath(mathString: string, time: moment.Moment, roundUp: boolean) { const dateTime = time; const len = mathString.length; let i = 0; @@ -89,7 +105,7 @@ function parseDateMath(mathString, time, roundUp) { const c = mathString.charAt(i++); let type; let num; - let unit; + let unit: Unit; if (c === '/') { type = 0; @@ -101,13 +117,13 @@ function parseDateMath(mathString, time, roundUp) { return; } - if (isNaN(mathString.charAt(i))) { + if (isNaN(mathString.charAt(i) as any)) { num = 1; } else if (mathString.length === 2) { num = mathString.charAt(i); } else { const numFrom = i; - while (!isNaN(mathString.charAt(i))) { + while (!isNaN(mathString.charAt(i) as any)) { i++; if (i >= len) return; } @@ -121,7 +137,7 @@ function parseDateMath(mathString, time, roundUp) { } } - unit = mathString.charAt(i++); + unit = mathString.charAt(i++) as Unit; // append additional characters in the unit for (let j = i; j < len; j++) { @@ -138,12 +154,12 @@ function parseDateMath(mathString, time, roundUp) { return; } else { if (type === 0) { - if (roundUp) dateTime.endOf(unit); - else dateTime.startOf(unit); + if (roundUp) dateTime.endOf(unit as any); + else dateTime.startOf(unit as any); } else if (type === 1) { - dateTime.add(num, unit); + dateTime.add(num as any, unit); } else if (type === 2) { - dateTime.subtract(num, unit); + dateTime.subtract(num as any, unit); } } } @@ -151,8 +167,9 @@ function parseDateMath(mathString, time, roundUp) { return dateTime; } -module.exports = { - parse: parse, +// eslint-disable-next-line import/no-default-export +export default { + parse, unitsMap: Object.freeze(unitsMap), units: Object.freeze(units), unitsAsc: Object.freeze(unitsAsc), diff --git a/packages/elastic-datemath/tsconfig.json b/packages/elastic-datemath/tsconfig.json index cbfe1e80474336..6f04bee983a9e5 100644 --- a/packages/elastic-datemath/tsconfig.json +++ b/packages/elastic-datemath/tsconfig.json @@ -1,9 +1,17 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { - "tsBuildInfoFile": "../../build/tsbuildinfo/packages/elastic-datemath" + "incremental": false, + "outDir": "./target", + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "sourceRoot": "../../../../packages/elastic-datemath/src", + "types": [ + "node" + ] }, "include": [ - "index.d.ts" + "src/index.ts" ] } diff --git a/packages/kbn-ace/package.json b/packages/kbn-ace/package.json index f7ca76b35e7c2e..30f37b4786f367 100644 --- a/packages/kbn-ace/package.json +++ b/packages/kbn-ace/package.json @@ -3,6 +3,7 @@ "version": "1.0.0", "private": true, "main": "./target/index.js", + "types": "./target/index.d.ts", "license": "SSPL-1.0 OR Elastic License 2.0", "scripts": { "build": "node ./scripts/build.js", diff --git a/packages/kbn-ace/tsconfig.json b/packages/kbn-ace/tsconfig.json index 6d3f433c6a6d10..9eef1ec56c6a2b 100644 --- a/packages/kbn-ace/tsconfig.json +++ b/packages/kbn-ace/tsconfig.json @@ -1,13 +1,15 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { + "incremental": false, "outDir": "./target", "declaration": true, + "declarationMap": true, "sourceMap": true, + "sourceRoot": "../../../../packages/kbn-ace/src", "types": [ - "jest", "node" - ] + ], }, "include": [ "src/**/*" diff --git a/packages/kbn-analytics/tsconfig.json b/packages/kbn-analytics/tsconfig.json index 861e0204a31a28..c2e579e7fdbead 100644 --- a/packages/kbn-analytics/tsconfig.json +++ b/packages/kbn-analytics/tsconfig.json @@ -1,20 +1,19 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { - "declaration": true, - "emitDeclarationOnly": true, + "incremental": false, "outDir": "./target/types", "stripInternal": true, + "emitDeclarationOnly": true, + "declaration": true, "declarationMap": true, + "sourceMap": true, + "sourceRoot": "../../../../../packages/kbn-analytics/src", "types": [ - "jest", "node" ] }, "include": [ "src/**/*" - ], - "exclude": [ - "target" ] } diff --git a/packages/kbn-apm-config-loader/__fixtures__/config.yml b/packages/kbn-apm-config-loader/src/__fixtures__/config.yml similarity index 100% rename from packages/kbn-apm-config-loader/__fixtures__/config.yml rename to packages/kbn-apm-config-loader/src/__fixtures__/config.yml diff --git a/packages/kbn-apm-config-loader/__fixtures__/config_flat.yml b/packages/kbn-apm-config-loader/src/__fixtures__/config_flat.yml similarity index 100% rename from packages/kbn-apm-config-loader/__fixtures__/config_flat.yml rename to packages/kbn-apm-config-loader/src/__fixtures__/config_flat.yml diff --git a/packages/kbn-apm-config-loader/__fixtures__/en_var_ref_config.yml b/packages/kbn-apm-config-loader/src/__fixtures__/en_var_ref_config.yml similarity index 100% rename from packages/kbn-apm-config-loader/__fixtures__/en_var_ref_config.yml rename to packages/kbn-apm-config-loader/src/__fixtures__/en_var_ref_config.yml diff --git a/packages/kbn-apm-config-loader/__fixtures__/one.yml b/packages/kbn-apm-config-loader/src/__fixtures__/one.yml similarity index 100% rename from packages/kbn-apm-config-loader/__fixtures__/one.yml rename to packages/kbn-apm-config-loader/src/__fixtures__/one.yml diff --git a/packages/kbn-apm-config-loader/__fixtures__/two.yml b/packages/kbn-apm-config-loader/src/__fixtures__/two.yml similarity index 100% rename from packages/kbn-apm-config-loader/__fixtures__/two.yml rename to packages/kbn-apm-config-loader/src/__fixtures__/two.yml diff --git a/packages/kbn-apm-config-loader/src/utils/read_config.test.ts b/packages/kbn-apm-config-loader/src/utils/read_config.test.ts index 16fbb5ce7aed8c..2838738c0ab6c2 100644 --- a/packages/kbn-apm-config-loader/src/utils/read_config.test.ts +++ b/packages/kbn-apm-config-loader/src/utils/read_config.test.ts @@ -9,7 +9,7 @@ import { relative, resolve } from 'path'; import { getConfigFromFiles } from './read_config'; -const fixtureFile = (name: string) => resolve(__dirname, '..', '..', '__fixtures__', name); +const fixtureFile = (name: string) => resolve(__dirname, '..', '__fixtures__', name); test('reads single yaml from file system and parses to json', () => { const config = getConfigFromFiles([fixtureFile('config.yml')]); diff --git a/packages/kbn-apm-config-loader/tsconfig.json b/packages/kbn-apm-config-loader/tsconfig.json index ba00ddfa6adb6f..250195785b931e 100644 --- a/packages/kbn-apm-config-loader/tsconfig.json +++ b/packages/kbn-apm-config-loader/tsconfig.json @@ -1,12 +1,19 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { - "declaration": true, + "incremental": false, "outDir": "./target", "stripInternal": false, + "declaration": true, "declarationMap": true, - "types": ["jest", "node"] + "sourceMap": true, + "sourceRoot": "../../../../packages/kbn-apm-config-loader/src", + "types": [ + "jest", + "node" + ] }, - "include": ["./src/**/*.ts"], - "exclude": ["target"] + "include": [ + "src/**/*.ts" + ] } diff --git a/packages/kbn-apm-utils/tsconfig.json b/packages/kbn-apm-utils/tsconfig.json index e1f79b5ef394da..e08769aab65436 100644 --- a/packages/kbn-apm-utils/tsconfig.json +++ b/packages/kbn-apm-utils/tsconfig.json @@ -1,18 +1,18 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { - "declaration": true, + "incremental": false, "outDir": "./target", "stripInternal": false, + "declaration": true, "declarationMap": true, + "sourceMap": true, + "sourceRoot": "../../../../packages/kbn-apm-utils/src", "types": [ "node" ] }, "include": [ "./src/**/*.ts" - ], - "exclude": [ - "target" ] } diff --git a/packages/kbn-cli-dev-mode/tsconfig.json b/packages/kbn-cli-dev-mode/tsconfig.json index b2bdaf8ceea36e..4436d27dbff887 100644 --- a/packages/kbn-cli-dev-mode/tsconfig.json +++ b/packages/kbn-cli-dev-mode/tsconfig.json @@ -1,11 +1,18 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { - "declaration": true, + "incremental": false, "outDir": "./target", + "declaration": true, "declarationMap": true, - "types": ["jest", "node"] + "sourceMap": true, + "sourceRoot": "../../../../packages/kbn-cli-dev-mode/src", + "types": [ + "jest", + "node" + ] }, - "include": ["./src/**/*.ts"], - "exclude": ["target"] + "include": [ + "./src/**/*.ts" + ], } diff --git a/packages/kbn-config-schema/tsconfig.json b/packages/kbn-config-schema/tsconfig.json index 6a268f2e7c0165..d33683acded162 100644 --- a/packages/kbn-config-schema/tsconfig.json +++ b/packages/kbn-config-schema/tsconfig.json @@ -1,21 +1,21 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { - "declaration": true, - "declarationDir": "./target/types", + "incremental": false, "outDir": "./target/out", + "declarationDir": "./target/types", "stripInternal": true, + "declaration": true, "declarationMap": true, + "sourceMap": true, + "sourceRoot": "../../../../../packages/kbn-config-schema/src", "types": [ "jest", "node" ] }, "include": [ - "./types/joi.d.ts", - "./src/**/*.ts" - ], - "exclude": [ - "target" + "types/joi.d.ts", + "src/**/*.ts" ] } diff --git a/packages/kbn-config/__fixtures__/config.yml b/packages/kbn-config/src/__fixtures__/config.yml similarity index 100% rename from packages/kbn-config/__fixtures__/config.yml rename to packages/kbn-config/src/__fixtures__/config.yml diff --git a/packages/kbn-config/__fixtures__/config_flat.yml b/packages/kbn-config/src/__fixtures__/config_flat.yml similarity index 100% rename from packages/kbn-config/__fixtures__/config_flat.yml rename to packages/kbn-config/src/__fixtures__/config_flat.yml diff --git a/packages/kbn-config/__fixtures__/en_var_ref_config.yml b/packages/kbn-config/src/__fixtures__/en_var_ref_config.yml similarity index 100% rename from packages/kbn-config/__fixtures__/en_var_ref_config.yml rename to packages/kbn-config/src/__fixtures__/en_var_ref_config.yml diff --git a/packages/kbn-config/__fixtures__/one.yml b/packages/kbn-config/src/__fixtures__/one.yml similarity index 100% rename from packages/kbn-config/__fixtures__/one.yml rename to packages/kbn-config/src/__fixtures__/one.yml diff --git a/packages/kbn-config/__fixtures__/two.yml b/packages/kbn-config/src/__fixtures__/two.yml similarity index 100% rename from packages/kbn-config/__fixtures__/two.yml rename to packages/kbn-config/src/__fixtures__/two.yml diff --git a/packages/kbn-config/src/raw/read_config.test.ts b/packages/kbn-config/src/raw/read_config.test.ts index 3b56c69098d2ce..d428fa6b0a2a1b 100644 --- a/packages/kbn-config/src/raw/read_config.test.ts +++ b/packages/kbn-config/src/raw/read_config.test.ts @@ -9,7 +9,7 @@ import { relative, resolve } from 'path'; import { getConfigFromFiles } from './read_config'; -const fixtureFile = (name: string) => resolve(`${__dirname}/../../__fixtures__/${name}`); +const fixtureFile = (name: string) => resolve(`${__dirname}/../__fixtures__/${name}`); test('reads single yaml from file system and parses to json', () => { const config = getConfigFromFiles([fixtureFile('config.yml')]); diff --git a/packages/kbn-config/tsconfig.json b/packages/kbn-config/tsconfig.json index ba00ddfa6adb6f..4e1bf573f488a5 100644 --- a/packages/kbn-config/tsconfig.json +++ b/packages/kbn-config/tsconfig.json @@ -1,12 +1,22 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { - "declaration": true, + "incremental": false, "outDir": "./target", "stripInternal": false, + "declaration": true, "declarationMap": true, - "types": ["jest", "node"] + "sourceMap": true, + "sourceRoot": "../../../../packages/kbn-config/src", + "types": [ + "jest", + "node" + ] }, - "include": ["./src/**/*.ts"], - "exclude": ["target"] + "include": [ + "src/**/*.ts" + ], + "exclude": [ + "**/__fixtures__/**/*" + ] } diff --git a/packages/kbn-crypto/package.json b/packages/kbn-crypto/package.json index 6c7b3f3b0c719b..7e26b96218319d 100644 --- a/packages/kbn-crypto/package.json +++ b/packages/kbn-crypto/package.json @@ -4,6 +4,7 @@ "private": true, "license": "SSPL-1.0 OR Elastic License 2.0", "main": "./target/index.js", + "types": "./target/index.d.ts", "scripts": { "build": "../../node_modules/.bin/tsc", "kbn:bootstrap": "yarn build", diff --git a/packages/kbn-crypto/tsconfig.json b/packages/kbn-crypto/tsconfig.json index e9dd6313e6f79e..5005152cac7546 100644 --- a/packages/kbn-crypto/tsconfig.json +++ b/packages/kbn-crypto/tsconfig.json @@ -1,9 +1,12 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { - "outDir": "target", + "incremental": false, + "outDir": "./target", "declaration": true, - "declarationMap": true + "declarationMap": true, + "sourceMap": true, + "sourceRoot": "../../../../packages/kbn-crypto/src" }, "include": [ "src/**/*" diff --git a/packages/kbn-dev-utils/package.json b/packages/kbn-dev-utils/package.json index 7a2c3ce45a57d8..e1990fca4e0bbf 100644 --- a/packages/kbn-dev-utils/package.json +++ b/packages/kbn-dev-utils/package.json @@ -4,6 +4,7 @@ "private": true, "license": "SSPL-1.0 OR Elastic License 2.0", "main": "./target/index.js", + "types": "./target/index.d.ts", "scripts": { "build": "../../node_modules/.bin/tsc", "kbn:bootstrap": "yarn build", diff --git a/packages/kbn-dev-utils/tsconfig.json b/packages/kbn-dev-utils/tsconfig.json index 1c6c671d0b7683..65536c576b6791 100644 --- a/packages/kbn-dev-utils/tsconfig.json +++ b/packages/kbn-dev-utils/tsconfig.json @@ -1,10 +1,18 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { + "incremental": false, "outDir": "target", + "stripInternal": false, "target": "ES2019", "declaration": true, - "declarationMap": true + "declarationMap": true, + "sourceMap": true, + "sourceRoot": "../../../../packages/kbn-dev-utils/src", + "types": [ + "jest", + "node" + ] }, "include": [ "src/**/*" diff --git a/packages/kbn-docs-utils/package.json b/packages/kbn-docs-utils/package.json index 089732e9e6b40a..26a7fa0e8c9576 100644 --- a/packages/kbn-docs-utils/package.json +++ b/packages/kbn-docs-utils/package.json @@ -4,6 +4,7 @@ "license": "SSPL-1.0 OR Elastic License 2.0", "private": "true", "main": "target/index.js", + "types": "target/index.d.ts", "kibana": { "devOnly": true }, diff --git a/packages/kbn-docs-utils/tsconfig.json b/packages/kbn-docs-utils/tsconfig.json index 3c683f487b9f24..6f4a6fa2af8a55 100644 --- a/packages/kbn-docs-utils/tsconfig.json +++ b/packages/kbn-docs-utils/tsconfig.json @@ -1,10 +1,17 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { + "incremental": false, "outDir": "./target", + "target": "ES2019", "declaration": true, + "declarationMap": true, "sourceMap": true, - "target": "ES2019" + "sourceRoot": "../../../../packages/kbn-docs-utils/src", + "types": [ + "jest", + "node" + ] }, "include": [ "src/**/*" diff --git a/packages/kbn-es-archiver/package.json b/packages/kbn-es-archiver/package.json index 03ecee34be7e2c..047d1dd675d263 100644 --- a/packages/kbn-es-archiver/package.json +++ b/packages/kbn-es-archiver/package.json @@ -4,6 +4,7 @@ "license": "SSPL-1.0 OR Elastic License 2.0", "private": "true", "main": "target/index.js", + "types": "target/index.d.ts", "kibana": { "devOnly": true }, diff --git a/packages/kbn-es-archiver/tsconfig.json b/packages/kbn-es-archiver/tsconfig.json index 02209a29e58171..0950cd39d0bee4 100644 --- a/packages/kbn-es-archiver/tsconfig.json +++ b/packages/kbn-es-archiver/tsconfig.json @@ -1,10 +1,17 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { + "incremental": false, "outDir": "./target", + "target": "ES2019", "declaration": true, + "declarationMap": true, "sourceMap": true, - "target": "ES2019" + "sourceRoot": "../../../../packages/kbn-es-archiver/src", + "types": [ + "jest", + "node" + ] }, "include": [ "src/**/*" diff --git a/packages/kbn-i18n/tsconfig.json b/packages/kbn-i18n/tsconfig.json index c6380f1cde9697..9d4cb8c9b0972b 100644 --- a/packages/kbn-i18n/tsconfig.json +++ b/packages/kbn-i18n/tsconfig.json @@ -1,5 +1,18 @@ { "extends": "../../tsconfig.base.json", + "compilerOptions": { + "incremental": false, + "outDir": "./target/types", + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "sourceRoot": "../../../../../packages/kbn-i18n/src", + "types": [ + "jest", + "node" + ] + }, "include": [ "src/**/*.ts", "src/**/*.tsx", @@ -7,15 +20,6 @@ "types/intl_relativeformat.d.ts" ], "exclude": [ - "target" - ], - "compilerOptions": { - "declaration": true, - "emitDeclarationOnly": true, - "outDir": "./target/types", - "types": [ - "jest", - "node" - ] - } + "**/__fixtures__/**/*" + ] } diff --git a/packages/kbn-interpreter/common/package.json b/packages/kbn-interpreter/common/package.json index b569e42220f04b..62061138234d9f 100644 --- a/packages/kbn-interpreter/common/package.json +++ b/packages/kbn-interpreter/common/package.json @@ -1,5 +1,6 @@ { "private": true, "main": "../target/common/index.js", + "types": "../target/common/index.d.ts", "jsnext:main": "../src/common/index.js" } \ No newline at end of file diff --git a/packages/kbn-legacy-logging/package.json b/packages/kbn-legacy-logging/package.json index 1e3752eca67558..9450fd39607ea9 100644 --- a/packages/kbn-legacy-logging/package.json +++ b/packages/kbn-legacy-logging/package.json @@ -4,6 +4,7 @@ "private": true, "license": "SSPL-1.0 OR Elastic License 2.0", "main": "./target/index.js", + "types": "./target/index.d.ts", "scripts": { "build": "tsc", "kbn:bootstrap": "yarn build", diff --git a/packages/kbn-legacy-logging/tsconfig.json b/packages/kbn-legacy-logging/tsconfig.json index 8fd202a2dce8ba..5f8d38ec90bcd3 100644 --- a/packages/kbn-legacy-logging/tsconfig.json +++ b/packages/kbn-legacy-logging/tsconfig.json @@ -1,11 +1,19 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { + "incremental": false, "outDir": "target", "stripInternal": false, "declaration": true, "declarationMap": true, - "types": ["jest", "node"] + "sourceMap": true, + "sourceRoot": "../../../../packages/kbn-legacy-logging/src", + "types": [ + "jest", + "node" + ] }, - "include": ["./src/**/*"] + "include": [ + "src/**/*" + ] } diff --git a/packages/kbn-logging/package.json b/packages/kbn-logging/package.json index 8d3ffa09b083ef..c7db148c75a2a1 100644 --- a/packages/kbn-logging/package.json +++ b/packages/kbn-logging/package.json @@ -4,6 +4,7 @@ "private": true, "license": "SSPL-1.0 OR Elastic License 2.0", "main": "./target/index.js", + "types": "./target/index.d.ts", "scripts": { "build": "../../node_modules/.bin/tsc", "kbn:bootstrap": "yarn build", diff --git a/packages/kbn-logging/tsconfig.json b/packages/kbn-logging/tsconfig.json index c55c05de30a528..adec4c19660367 100644 --- a/packages/kbn-logging/tsconfig.json +++ b/packages/kbn-logging/tsconfig.json @@ -1,11 +1,19 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { + "incremental": false, "outDir": "target", "stripInternal": false, "declaration": true, "declarationMap": true, - "types": ["jest", "node"] + "sourceMap": true, + "sourceRoot": "../../../../packages/kbn-logging/src", + "types": [ + "jest", + "node" + ] }, - "include": ["./src/**/*.ts"] + "include": [ + "src/**/*.ts" + ] } diff --git a/packages/kbn-monaco/package.json b/packages/kbn-monaco/package.json index e99661f8db5986..bdf36915bab3ae 100644 --- a/packages/kbn-monaco/package.json +++ b/packages/kbn-monaco/package.json @@ -3,6 +3,7 @@ "version": "1.0.0", "private": true, "main": "./target/index.js", + "types": "./target/index.d.ts", "license": "SSPL-1.0 OR Elastic License 2.0", "scripts": { "build": "node ./scripts/build.js", diff --git a/packages/kbn-monaco/tsconfig.json b/packages/kbn-monaco/tsconfig.json index 6d3f433c6a6d10..e6ec96b12c6cf1 100644 --- a/packages/kbn-monaco/tsconfig.json +++ b/packages/kbn-monaco/tsconfig.json @@ -1,9 +1,12 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { + "incremental": false, "outDir": "./target", "declaration": true, + "declarationMap": true, "sourceMap": true, + "sourceRoot": "../../../../packages/kbn-monaco/src", "types": [ "jest", "node" diff --git a/packages/kbn-optimizer/package.json b/packages/kbn-optimizer/package.json index 29e33bf23424b0..ac73fbc0fc16a9 100644 --- a/packages/kbn-optimizer/package.json +++ b/packages/kbn-optimizer/package.json @@ -4,8 +4,9 @@ "private": true, "license": "SSPL-1.0 OR Elastic License 2.0", "main": "./target/index.js", + "types": "./target/index.d.ts", "scripts": { - "build": "../../node_modules/.bin/babel src --out-dir target --copy-files --delete-dir-on-start --extensions .ts --ignore *.test.ts --source-maps=inline", + "build": "../../node_modules/.bin/tsc", "kbn:bootstrap": "yarn build", "kbn:watch": "yarn build --watch" }, diff --git a/packages/kbn-optimizer/src/integration_tests/basic_optimization.test.ts b/packages/kbn-optimizer/src/integration_tests/basic_optimization.test.ts index a86f231b79806a..50c9e7e12904f7 100644 --- a/packages/kbn-optimizer/src/integration_tests/basic_optimization.test.ts +++ b/packages/kbn-optimizer/src/integration_tests/basic_optimization.test.ts @@ -16,7 +16,7 @@ import del from 'del'; import { tap, filter } from 'rxjs/operators'; import { REPO_ROOT } from '@kbn/utils'; import { ToolingLog } from '@kbn/dev-utils'; -import { runOptimizer, OptimizerConfig, OptimizerUpdate, logOptimizerState } from '@kbn/optimizer'; +import { runOptimizer, OptimizerConfig, OptimizerUpdate, logOptimizerState } from '../index'; import { allValuesFrom } from '../common'; @@ -135,7 +135,7 @@ it('builds expected bundles, saves bundle counts to metadata', async () => { /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo/public/ext.ts, /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo/public/index.ts, /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo/public/lib.ts, - /packages/kbn-optimizer/target/worker/entry_point_creator.js, + /packages/kbn-optimizer/src/worker/entry_point_creator.ts, /packages/kbn-ui-shared-deps/public_path_module_creator.js, ] `); @@ -161,7 +161,7 @@ it('builds expected bundles, saves bundle counts to metadata', async () => { /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/bar/public/lib.ts, /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/src/core/public/core_app/styles/_globals_v8dark.scss, /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/src/core/public/core_app/styles/_globals_v8light.scss, - /packages/kbn-optimizer/target/worker/entry_point_creator.js, + /packages/kbn-optimizer/src/worker/entry_point_creator.ts, /packages/kbn-ui-shared-deps/public_path_module_creator.js, ] `); @@ -175,7 +175,7 @@ it('builds expected bundles, saves bundle counts to metadata', async () => { Array [ /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/x-pack/baz/kibana.json, /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/x-pack/baz/public/index.ts, - /packages/kbn-optimizer/target/worker/entry_point_creator.js, + /packages/kbn-optimizer/src/worker/entry_point_creator.ts, /packages/kbn-ui-shared-deps/public_path_module_creator.js, ] `); diff --git a/packages/kbn-optimizer/src/optimizer/observe_worker.ts b/packages/kbn-optimizer/src/optimizer/observe_worker.ts index edf4545ae52b34..9f0ed9af556fb2 100644 --- a/packages/kbn-optimizer/src/optimizer/observe_worker.ts +++ b/packages/kbn-optimizer/src/optimizer/observe_worker.ts @@ -61,18 +61,26 @@ function usingWorkerProc( ) { return Rx.using( (): ProcResource => { - const proc = execa.node(require.resolve('../worker/run_worker'), [], { - nodeOptions: [ - ...process.execArgv, - ...(inspectFlag && config.inspectWorkers - ? [`${inspectFlag}=${inspectPortCounter++}`] - : []), - ...(config.maxWorkerCount <= 3 ? ['--max-old-space-size=2048'] : []), - ], - buffer: false, - stderr: 'pipe', - stdout: 'pipe', - }); + const workerPath = require.resolve('../worker/run_worker'); + const proc = execa.node( + workerPath.endsWith('.ts') + ? require.resolve('../worker/run_worker_from_source') // workerFromSourcePath + : workerPath, + [], + { + nodeOptions: [ + '--preserve-symlinks', + '--preserve-symlinks-main', + ...(inspectFlag && config.inspectWorkers + ? [`${inspectFlag}=${inspectPortCounter++}`] + : []), + ...(config.maxWorkerCount <= 3 ? ['--max-old-space-size=2048'] : []), + ], + buffer: false, + stderr: 'pipe', + stdout: 'pipe', + } + ); return { proc, diff --git a/packages/kbn-optimizer/src/worker/bundle_ref_module.ts b/packages/kbn-optimizer/src/worker/bundle_ref_module.ts index 563b4ecb4bc37b..f7604f0f78f719 100644 --- a/packages/kbn-optimizer/src/worker/bundle_ref_module.ts +++ b/packages/kbn-optimizer/src/worker/bundle_ref_module.ts @@ -16,7 +16,6 @@ export class BundleRefModule extends Module { public built = false; public buildMeta?: any; public buildInfo?: any; - public exportsArgument = '__webpack_exports__'; constructor(public readonly ref: BundleRef) { super('kbn/bundleRef', null); @@ -45,7 +44,9 @@ export class BundleRefModule extends Module { build(_: any, __: any, ___: any, ____: any, callback: () => void) { this.built = true; this.buildMeta = {}; - this.buildInfo = {}; + this.buildInfo = { + exportsArgument: '__webpack_exports__', + }; callback(); } diff --git a/packages/kbn-optimizer/index.d.ts b/packages/kbn-optimizer/src/worker/run_worker_from_source.js similarity index 80% rename from packages/kbn-optimizer/index.d.ts rename to packages/kbn-optimizer/src/worker/run_worker_from_source.js index 004ac67f4b0c41..bebe984a447d6b 100644 --- a/packages/kbn-optimizer/index.d.ts +++ b/packages/kbn-optimizer/src/worker/run_worker_from_source.js @@ -6,4 +6,5 @@ * Side Public License, v 1. */ -export * from './src/index'; +require('@kbn/optimizer').registerNodeAutoTranspilation(); +require('./run_worker'); diff --git a/packages/kbn-optimizer/tsconfig.json b/packages/kbn-optimizer/tsconfig.json index 20b06b5658cbcb..f2d508cf14a55e 100644 --- a/packages/kbn-optimizer/tsconfig.json +++ b/packages/kbn-optimizer/tsconfig.json @@ -1,10 +1,17 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { - "tsBuildInfoFile": "../../build/tsbuildinfo/packages/kbn-optimizer" + "incremental": false, + "outDir": "./target", + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "sourceRoot": "../../../../packages/kbn-optimizer/src" }, "include": [ - "index.d.ts", "src/**/*" + ], + "exclude": [ + "**/__fixtures__/**/*" ] } diff --git a/packages/kbn-plugin-generator/package.json b/packages/kbn-plugin-generator/package.json index a0a18bfe7d1cb7..ae4dfbc670f195 100644 --- a/packages/kbn-plugin-generator/package.json +++ b/packages/kbn-plugin-generator/package.json @@ -4,6 +4,7 @@ "private": true, "license": "SSPL-1.0 OR Elastic License 2.0", "main": "target/index.js", + "types": "target/index.d.ts", "scripts": { "kbn:bootstrap": "node scripts/build", "kbn:watch": "node scripts/build --watch" diff --git a/packages/kbn-plugin-generator/tsconfig.json b/packages/kbn-plugin-generator/tsconfig.json index c54ff041d7065b..5e885527a76083 100644 --- a/packages/kbn-plugin-generator/tsconfig.json +++ b/packages/kbn-plugin-generator/tsconfig.json @@ -1,12 +1,22 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { + "incremental": false, "outDir": "target", "target": "ES2019", "declaration": true, "declarationMap": true, - "sourceMap": true + "sourceMap": true, + "sourceRoot": "../../../../packages/kbn-plugin-generator/src", + "types": [ + "jest", + "node" + ] }, - "include": ["src/**/*"], - "exclude": ["src/template/*"] + "include": [ + "src/**/*" + ], + "exclude": [ + "src/template/*" + ], } diff --git a/packages/kbn-plugin-helpers/package.json b/packages/kbn-plugin-helpers/package.json index cc845ef9d027f0..6b9dd4d51baf94 100644 --- a/packages/kbn-plugin-helpers/package.json +++ b/packages/kbn-plugin-helpers/package.json @@ -8,6 +8,7 @@ "devOnly": true }, "main": "target/index.js", + "types": "target/index.d.ts", "bin": { "plugin-helpers": "bin/plugin-helpers.js" }, diff --git a/packages/kbn-plugin-helpers/tsconfig.json b/packages/kbn-plugin-helpers/tsconfig.json index 651bc79d6e7071..87d11843f398af 100644 --- a/packages/kbn-plugin-helpers/tsconfig.json +++ b/packages/kbn-plugin-helpers/tsconfig.json @@ -1,10 +1,17 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { + "incremental": false, "outDir": "target", + "target": "ES2018", "declaration": true, + "declarationMap": true, "sourceMap": true, - "target": "ES2018" + "sourceRoot": "../../../../packages/kbn-plugin-helpers/src", + "types": [ + "jest", + "node" + ] }, "include": [ "src/**/*" diff --git a/packages/kbn-pm/dist/index.js b/packages/kbn-pm/dist/index.js index 9bf332bf823199..bcb0b6da2a2f80 100644 --- a/packages/kbn-pm/dist/index.js +++ b/packages/kbn-pm/dist/index.js @@ -550,7 +550,7 @@ Object.defineProperty(exports, "pickLevelFromFlags", { enumerable: true, get: fu Object.defineProperty(exports, "parseLogLevel", { enumerable: true, get: function () { return log_levels_1.parseLogLevel; } }); var tooling_log_collecting_writer_1 = __webpack_require__(127); Object.defineProperty(exports, "ToolingLogCollectingWriter", { enumerable: true, get: function () { return tooling_log_collecting_writer_1.ToolingLogCollectingWriter; } }); - +//# sourceMappingURL=index.js.map /***/ }), /* 6 */ @@ -628,7 +628,7 @@ class ToolingLog { } } exports.ToolingLog = ToolingLog; - +//# sourceMappingURL=tooling_log.js.map /***/ }), /* 7 */ @@ -6749,7 +6749,7 @@ class ToolingLogTextWriter { } } exports.ToolingLogTextWriter = ToolingLogTextWriter; - +//# sourceMappingURL=tooling_log_text_writer.js.map /***/ }), /* 112 */ @@ -8790,7 +8790,7 @@ function parseLogLevel(name) { }; } exports.parseLogLevel = parseLogLevel; - +//# sourceMappingURL=log_levels.js.map /***/ }), /* 127 */ @@ -8823,7 +8823,7 @@ class ToolingLogCollectingWriter extends tooling_log_text_writer_1.ToolingLogTex } } exports.ToolingLogCollectingWriter = ToolingLogCollectingWriter; - +//# sourceMappingURL=tooling_log_collecting_writer.js.map /***/ }), /* 128 */ @@ -54377,7 +54377,7 @@ Object.defineProperty(exports, "__esModule", { value: true }); const tslib_1 = __webpack_require__(7); tslib_1.__exportStar(__webpack_require__(476), exports); tslib_1.__exportStar(__webpack_require__(477), exports); - +//# sourceMappingURL=index.js.map /***/ }), /* 476 */ @@ -54436,7 +54436,7 @@ function observeLines(readable) { operators_1.catchError(() => Rx.empty()))); } exports.observeLines = observeLines; - +//# sourceMappingURL=observe_lines.js.map /***/ }), /* 477 */ @@ -54465,7 +54465,7 @@ function observeReadable(readable) { return Rx.race(Rx.fromEvent(readable, 'end').pipe(operators_1.first(), operators_1.ignoreElements()), Rx.fromEvent(readable, 'error').pipe(operators_1.first(), operators_1.mergeMap((err) => Rx.throwError(err)))); } exports.observeReadable = observeReadable; - +//# sourceMappingURL=observe_readable.js.map /***/ }), /* 478 */ @@ -59798,7 +59798,7 @@ class CiStatsReporter { } } exports.CiStatsReporter = CiStatsReporter; - +//# sourceMappingURL=ci_stats_reporter.js.map /***/ }), /* 516 */ @@ -63258,7 +63258,7 @@ function parseConfig(log) { return; } exports.parseConfig = parseConfig; - +//# sourceMappingURL=ci_stats_config.js.map /***/ }), /* 557 */ diff --git a/packages/kbn-pm/tsconfig.json b/packages/kbn-pm/tsconfig.json index 175c4701f2e5ba..558cff6556ff6b 100644 --- a/packages/kbn-pm/tsconfig.json +++ b/packages/kbn-pm/tsconfig.json @@ -1,16 +1,14 @@ { "extends": "../../tsconfig.base.json", - "include": [ - "./index.d.ts", - "./src/**/*.ts", - "./dist/*.d.ts" - ], - "exclude": [], "compilerOptions": { "tsBuildInfoFile": "../../build/tsbuildinfo/packages/kbn-pm", "types": [ "jest", "node" ] - } + }, + "include": [ + "./index.d.ts", + "./src/**/*.ts" + ] } diff --git a/packages/kbn-server-http-tools/package.json b/packages/kbn-server-http-tools/package.json index a8f99689f33354..6c65a0dd6e475e 100644 --- a/packages/kbn-server-http-tools/package.json +++ b/packages/kbn-server-http-tools/package.json @@ -1,6 +1,7 @@ { "name": "@kbn/server-http-tools", "main": "./target/index.js", + "types": "./target/index.d.ts", "version": "1.0.0", "license": "SSPL-1.0 OR Elastic License 2.0", "private": true, diff --git a/packages/kbn-server-http-tools/tsconfig.json b/packages/kbn-server-http-tools/tsconfig.json index ec84b963aed700..2f3e4626a04ce7 100644 --- a/packages/kbn-server-http-tools/tsconfig.json +++ b/packages/kbn-server-http-tools/tsconfig.json @@ -1,14 +1,14 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { - "outDir": "target", + "incremental": false, + "outDir": "./target", "declaration": true, - "declarationMap": true + "declarationMap": true, + "sourceMap": true, + "sourceRoot": "../../../../packages/kbn-server-http-tools/src" }, "include": [ "src/**/*" - ], - "dependencies": { - "@kbn/std": "link:../kbn-std" - } + ] } diff --git a/packages/kbn-std/tsconfig.json b/packages/kbn-std/tsconfig.json index fd186a6e43d1c0..d2ed46dcad6f83 100644 --- a/packages/kbn-std/tsconfig.json +++ b/packages/kbn-std/tsconfig.json @@ -1,13 +1,23 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { - "declaration": true, + "incremental": false, "declarationDir": "./target", "outDir": "./target", "stripInternal": true, + "declaration": true, "declarationMap": true, - "types": ["jest", "node"] + "sourceMap": true, + "sourceRoot": "../../../../packages/kbn-std/src", + "types": [ + "jest", + "node" + ] }, - "include": ["./src/**/*.ts"], - "exclude": ["target"] + "include": [ + "./src/**/*.ts" + ], + "exclude": [ + "**/__fixture__/**/*" + ] } diff --git a/packages/kbn-storybook/package.json b/packages/kbn-storybook/package.json index 75801948bb20b4..fdc7359aab58dc 100644 --- a/packages/kbn-storybook/package.json +++ b/packages/kbn-storybook/package.json @@ -4,6 +4,7 @@ "private": true, "license": "SSPL-1.0 OR Elastic License 2.0", "main": "./target/index.js", + "types": "./target/index.d.ts", "kibana": { "devOnly": true }, diff --git a/packages/kbn-storybook/tsconfig.json b/packages/kbn-storybook/tsconfig.json index 814a3963c9f495..db10d4630ff9c8 100644 --- a/packages/kbn-storybook/tsconfig.json +++ b/packages/kbn-storybook/tsconfig.json @@ -1,9 +1,19 @@ { "extends": "../../tsconfig.json", "compilerOptions": { - "declaration": true, + "incremental": false, "outDir": "target", - "skipLibCheck": true + "skipLibCheck": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "sourceRoot": "../../../../packages/kbn-storybook", + "types": [ + "node" + ] }, - "include": ["*.ts", "lib/*.ts"] + "include": [ + "*.ts", + "lib/*.ts" + ] } diff --git a/packages/kbn-telemetry-tools/package.json b/packages/kbn-telemetry-tools/package.json index 28d67c73eb49e1..2ae1f596a1c689 100644 --- a/packages/kbn-telemetry-tools/package.json +++ b/packages/kbn-telemetry-tools/package.json @@ -3,6 +3,7 @@ "version": "1.0.0", "license": "SSPL-1.0 OR Elastic License 2.0", "main": "./target/index.js", + "types": "./target/index.d.ts", "private": true, "kibana": { "devOnly": true diff --git a/packages/kbn-telemetry-tools/tsconfig.json b/packages/kbn-telemetry-tools/tsconfig.json index 98512053a5c92b..39946fe9907e55 100644 --- a/packages/kbn-telemetry-tools/tsconfig.json +++ b/packages/kbn-telemetry-tools/tsconfig.json @@ -1,7 +1,12 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { - "tsBuildInfoFile": "../../build/tsbuildinfo/packages/kbn-telemetry-tools" + "incremental": false, + "outDir": "./target", + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "sourceRoot": "../../../../packages/kbn-telemetry-tools/src" }, "include": [ "src/**/*", diff --git a/packages/kbn-test/index.d.ts b/packages/kbn-test/index.d.ts deleted file mode 100644 index 004ac67f4b0c41..00000000000000 --- a/packages/kbn-test/index.d.ts +++ /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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export * from './src/index'; diff --git a/packages/kbn-test/package.json b/packages/kbn-test/package.json index 0f0ba8d79a1c1c..a2dc8f84cfb513 100644 --- a/packages/kbn-test/package.json +++ b/packages/kbn-test/package.json @@ -4,6 +4,7 @@ "private": true, "license": "SSPL-1.0 OR Elastic License 2.0", "main": "./target/index.js", + "types": "./target/types/index.d.ts", "scripts": { "build": "node scripts/build", "kbn:bootstrap": "node scripts/build --source-maps", diff --git a/packages/kbn-test/src/functional_test_runner/fake_mocha_types.d.ts b/packages/kbn-test/src/functional_test_runner/fake_mocha_types.ts similarity index 100% rename from packages/kbn-test/src/functional_test_runner/fake_mocha_types.d.ts rename to packages/kbn-test/src/functional_test_runner/fake_mocha_types.ts diff --git a/packages/kbn-test/src/functional_test_runner/lib/index.ts b/packages/kbn-test/src/functional_test_runner/lib/index.ts index eef9e833fe5a86..1cb1e58a265d57 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/index.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/index.ts @@ -10,6 +10,7 @@ export { Lifecycle } from './lifecycle'; export { LifecyclePhase } from './lifecycle_phase'; export { readConfigFile, Config } from './config'; export { readProviderSpec, ProviderCollection } from './providers'; +// @internal export { runTests, setupMocha } from './mocha'; export { FailureMetadata } from './failure_metadata'; export * from './docker_servers'; diff --git a/packages/kbn-test/src/functional_test_runner/lib/mocha/index.ts b/packages/kbn-test/src/functional_test_runner/lib/mocha/index.ts index de55df34fa88b6..4f27980db61d18 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/mocha/index.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/mocha/index.ts @@ -7,5 +7,7 @@ */ // @ts-ignore will be replaced shortly +// @internal export { setupMocha } from './setup_mocha'; +// @internal export { runTests } from './run_tests'; diff --git a/packages/kbn-test/src/index.ts b/packages/kbn-test/src/index.ts index 919dc8b4477f3a..ef167bc5d7819e 100644 --- a/packages/kbn-test/src/index.ts +++ b/packages/kbn-test/src/index.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +// @internal import { runTestsCli, processRunTestsCliOptions, @@ -14,27 +15,34 @@ import { // @ts-ignore not typed yet } from './functional_tests/cli'; +// @internal export { runTestsCli, processRunTestsCliOptions, startServersCli, processStartServersCliOptions }; // @ts-ignore not typed yet +// @internal export { runTests, startServers } from './functional_tests/tasks'; // @ts-ignore not typed yet +// @internal export { KIBANA_ROOT } from './functional_tests/lib/paths'; // @ts-ignore not typed yet +// @internal export { esTestConfig, createLegacyEsTestCluster } from './legacy_es'; // @ts-ignore not typed yet +// @internal export { kbnTestConfig, kibanaServerTestUser, kibanaTestUser, adminTestUser } from './kbn'; // @ts-ignore not typed yet +// @internal export { setupUsers, DEFAULT_SUPERUSER_PASS } from './functional_tests/lib/auth'; export { readConfigFile } from './functional_test_runner/lib/config/read_config_file'; export { runFtrCli } from './functional_test_runner/cli'; +// @internal export { setupJUnitReportGeneration, escapeCdata } from './mocha'; export { runFailedTestsReporterCli } from './failed_tests_reporter'; diff --git a/packages/kbn-test/src/kbn_archiver_cli.ts b/packages/kbn-test/src/kbn_archiver_cli.ts index 98bfa6eaa4046d..04581a83546686 100644 --- a/packages/kbn-test/src/kbn_archiver_cli.ts +++ b/packages/kbn-test/src/kbn_archiver_cli.ts @@ -10,7 +10,7 @@ import Path from 'path'; import Url from 'url'; import { RunWithCommands, createFlagError, Flags } from '@kbn/dev-utils'; -import { KbnClient } from '@kbn/test'; +import { KbnClient } from './kbn_client'; import { readConfigFile } from './functional_test_runner'; diff --git a/packages/kbn-test/src/mocha/index.ts b/packages/kbn-test/src/mocha/index.ts index 1cff5202f33b98..4ada51c7ae0132 100644 --- a/packages/kbn-test/src/mocha/index.ts +++ b/packages/kbn-test/src/mocha/index.ts @@ -7,8 +7,11 @@ */ // @ts-ignore not typed yet +// @internal export { setupJUnitReportGeneration } from './junit_report_generation'; // @ts-ignore not typed yet +// @internal export { recordLog, snapshotLogsForRunnable } from './log_cache'; // @ts-ignore not typed yet +// @internal export { escapeCdata } from './xml'; diff --git a/packages/kbn-test/tsconfig.json b/packages/kbn-test/tsconfig.json index 6d94389f82caaf..8536ad7e0c12f5 100644 --- a/packages/kbn-test/tsconfig.json +++ b/packages/kbn-test/tsconfig.json @@ -1,22 +1,26 @@ { "extends": "../../tsconfig.base.json", - "include": [ - "types/**/*", - "src/**/*", - "index.d.ts" - ], - "exclude": [ - "types/ftr_globals/**/*" - ], "compilerOptions": { - "declaration": true, - "emitDeclarationOnly": true, + "incremental": false, "outDir": "./target/types", + "stripInternal": true, + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "sourceRoot": "../../../../../packages/kbn-test/src", "types": [ "jest", "node" ], - "stripInternal": true, - "declarationMap": true - } + }, + "include": [ + "types/**/*", + "src/**/*", + "index.d.ts" + ], + "exclude": [ + "types/ftr_globals/**/*", + "**/__fixtures__/**/*" + ] } diff --git a/packages/kbn-utility-types/package.json b/packages/kbn-utility-types/package.json index 33419ee0f1ec40..ad7dcc6b906c31 100644 --- a/packages/kbn-utility-types/package.json +++ b/packages/kbn-utility-types/package.json @@ -3,7 +3,7 @@ "version": "1.0.0", "private": true, "license": "SSPL-1.0 OR Elastic License 2.0", - "main": "target", + "main": "target/index.js", "types": "target/index.d.ts", "kibana": { "devOnly": false diff --git a/packages/kbn-utility-types/tsconfig.json b/packages/kbn-utility-types/tsconfig.json index c2d206526e6f43..cfa782e5d38d27 100644 --- a/packages/kbn-utility-types/tsconfig.json +++ b/packages/kbn-utility-types/tsconfig.json @@ -1,18 +1,22 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { - "declaration": true, - "declarationDir": "./target", + "incremental": false, "outDir": "./target", + "declarationDir": "./target", "stripInternal": true, + "declaration": true, "declarationMap": true, + "sourceMap": true, + "sourceRoot": "../../../../packages/kbn-utility-types", "types": [ - "node", - "jest" + "jest", + "node" ] }, - "include": ["index.ts", "jest/**/*", "test-d/**/*"], - "exclude": [ - "target" + "include": [ + "index.ts", + "jest/**/*", + "test-d/**/*" ] } diff --git a/packages/kbn-utils/package.json b/packages/kbn-utils/package.json index 902eef82736fe0..b6bb7759c40efe 100644 --- a/packages/kbn-utils/package.json +++ b/packages/kbn-utils/package.json @@ -1,6 +1,7 @@ { "name": "@kbn/utils", "main": "./target/index.js", + "types": "./target/index.d.ts", "version": "1.0.0", "license": "SSPL-1.0 OR Elastic License 2.0", "private": true, diff --git a/packages/kbn-utils/tsconfig.json b/packages/kbn-utils/tsconfig.json index e9dd6313e6f79e..e6c83767c30dc0 100644 --- a/packages/kbn-utils/tsconfig.json +++ b/packages/kbn-utils/tsconfig.json @@ -1,9 +1,16 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { + "incremental": false, "outDir": "target", "declaration": true, - "declarationMap": true + "declarationMap": true, + "sourceMap": true, + "sourceRoot": "../../../../packages/kbn-utils/src", + "types": [ + "jest", + "node" + ] }, "include": [ "src/**/*" diff --git a/src/core/test_helpers/kbn_server.ts b/src/core/test_helpers/kbn_server.ts index d702fed73778f1..1844b5de3dc354 100644 --- a/src/core/test_helpers/kbn_server.ts +++ b/src/core/test_helpers/kbn_server.ts @@ -9,12 +9,19 @@ import { Client } from 'elasticsearch'; import { ToolingLog, REPO_ROOT } from '@kbn/dev-utils'; import { + // @ts-expect-error https://github.com/elastic/kibana/issues/95679 createLegacyEsTestCluster, + // @ts-expect-error https://github.com/elastic/kibana/issues/95679 DEFAULT_SUPERUSER_PASS, + // @ts-expect-error https://github.com/elastic/kibana/issues/95679 esTestConfig, + // @ts-expect-error https://github.com/elastic/kibana/issues/95679 kbnTestConfig, + // @ts-expect-error https://github.com/elastic/kibana/issues/95679 kibanaServerTestUser, + // @ts-expect-error https://github.com/elastic/kibana/issues/95679 kibanaTestUser, + // @ts-expect-error https://github.com/elastic/kibana/issues/95679 setupUsers, } from '@kbn/test'; import { defaultsDeep, get } from 'lodash'; diff --git a/tsconfig.base.json b/tsconfig.base.json index 865806cffe5bb1..da4de5ef3712b3 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -38,6 +38,8 @@ "moduleResolution": "node", // "resolveJsonModule" allows for importing, extracting types from and generating .json files. "resolveJsonModule": true, + // Do not resolve symlinks to their real path; treat a symlinked file like a real one. + "preserveSymlinks": true, // Disallow inconsistently-cased references to the same file. "forceConsistentCasingInFileNames": false, // Forbid unused local variables as the rule was deprecated by ts-lint diff --git a/x-pack/test/examples/config.ts b/x-pack/test/examples/config.ts index dd087772ae52c8..fe1b5ce299447e 100644 --- a/x-pack/test/examples/config.ts +++ b/x-pack/test/examples/config.ts @@ -8,6 +8,7 @@ import { FtrConfigProviderContext } from '@kbn/test/types/ftr'; import { resolve } from 'path'; import fs from 'fs'; +// @ts-expect-error https://github.com/elastic/kibana/issues/95679 import { KIBANA_ROOT } from '@kbn/test'; export default async function ({ readConfigFile }: FtrConfigProviderContext) { diff --git a/x-pack/test/functional/page_objects/security_page.ts b/x-pack/test/functional/page_objects/security_page.ts index 3c0ef7348a4b3c..f153050018609c 100644 --- a/x-pack/test/functional/page_objects/security_page.ts +++ b/x-pack/test/functional/page_objects/security_page.ts @@ -5,6 +5,7 @@ * 2.0. */ +// @ts-expect-error https://github.com/elastic/kibana/issues/95679 import { adminTestUser } from '@kbn/test'; import { FtrProviderContext } from '../ftr_provider_context'; import { AuthenticatedUser, Role } from '../../../plugins/security/common/model'; diff --git a/x-pack/test/functional_cors/config.ts b/x-pack/test/functional_cors/config.ts index 81870a948dc151..42e7771b144018 100644 --- a/x-pack/test/functional_cors/config.ts +++ b/x-pack/test/functional_cors/config.ts @@ -8,6 +8,7 @@ import Url from 'url'; import Path from 'path'; import type { FtrConfigProviderContext } from '@kbn/test/types/ftr'; +// @ts-expect-error https://github.com/elastic/kibana/issues/95679 import { kbnTestConfig } from '@kbn/test'; import { pageObjects } from '../functional/page_objects'; diff --git a/x-pack/test/functional_cors/plugins/kibana_cors_test/server/plugin.ts b/x-pack/test/functional_cors/plugins/kibana_cors_test/server/plugin.ts index e6c3f4b05aabd8..e128ec6f13e772 100644 --- a/x-pack/test/functional_cors/plugins/kibana_cors_test/server/plugin.ts +++ b/x-pack/test/functional_cors/plugins/kibana_cors_test/server/plugin.ts @@ -6,6 +6,7 @@ */ import Hapi from '@hapi/hapi'; +// @ts-expect-error https://github.com/elastic/kibana/issues/95679 import { kbnTestConfig } from '@kbn/test'; import { take } from 'rxjs/operators'; import Url from 'url'; diff --git a/x-pack/test/licensing_plugin/config.public.ts b/x-pack/test/licensing_plugin/config.public.ts index 35f3bfa11fea06..0de536d7125ca6 100644 --- a/x-pack/test/licensing_plugin/config.public.ts +++ b/x-pack/test/licensing_plugin/config.public.ts @@ -6,6 +6,7 @@ */ import path from 'path'; +// @ts-expect-error https://github.com/elastic/kibana/issues/95679 import { KIBANA_ROOT } from '@kbn/test'; import { FtrConfigProviderContext } from '@kbn/test/types/ftr'; diff --git a/x-pack/test/plugin_functional/config.ts b/x-pack/test/plugin_functional/config.ts index 9261650bb5d4ef..5b846e414bd4c1 100644 --- a/x-pack/test/plugin_functional/config.ts +++ b/x-pack/test/plugin_functional/config.ts @@ -7,6 +7,7 @@ import { resolve } from 'path'; import fs from 'fs'; +// @ts-expect-error https://github.com/elastic/kibana/issues/95679 import { KIBANA_ROOT } from '@kbn/test'; import { FtrConfigProviderContext } from '@kbn/test/types/ftr'; import { services } from './services'; diff --git a/x-pack/test/reporting_api_integration/reporting_and_security.config.ts b/x-pack/test/reporting_api_integration/reporting_and_security.config.ts index 6627cb3be5ed50..ddd6fe046dd31b 100644 --- a/x-pack/test/reporting_api_integration/reporting_and_security.config.ts +++ b/x-pack/test/reporting_api_integration/reporting_and_security.config.ts @@ -5,6 +5,7 @@ * 2.0. */ +// @ts-expect-error https://github.com/elastic/kibana/issues/95679 import { esTestConfig, kbnTestConfig, kibanaServerTestUser } from '@kbn/test'; import { FtrConfigProviderContext } from '@kbn/test/types/ftr'; import { format as formatUrl } from 'url'; diff --git a/x-pack/test/reporting_api_integration/reporting_without_security.config.ts b/x-pack/test/reporting_api_integration/reporting_without_security.config.ts index 59d6074d9d8caf..20f9ff1b10592f 100644 --- a/x-pack/test/reporting_api_integration/reporting_without_security.config.ts +++ b/x-pack/test/reporting_api_integration/reporting_without_security.config.ts @@ -5,6 +5,7 @@ * 2.0. */ +// @ts-expect-error https://github.com/elastic/kibana/issues/95679 import { esTestConfig, kbnTestConfig } from '@kbn/test'; import { FtrConfigProviderContext } from '@kbn/test/types/ftr'; import { format as formatUrl } from 'url'; diff --git a/x-pack/test/security_api_integration/tests/anonymous/login.ts b/x-pack/test/security_api_integration/tests/anonymous/login.ts index 183016316fcdbf..3d1a05583e904c 100644 --- a/x-pack/test/security_api_integration/tests/anonymous/login.ts +++ b/x-pack/test/security_api_integration/tests/anonymous/login.ts @@ -7,6 +7,7 @@ import expect from '@kbn/expect'; import request, { Cookie } from 'request'; +// @ts-expect-error https://github.com/elastic/kibana/issues/95679 import { adminTestUser } from '@kbn/test'; import { FtrProviderContext } from '../../ftr_provider_context'; diff --git a/x-pack/test/security_api_integration/tests/kerberos/kerberos_login.ts b/x-pack/test/security_api_integration/tests/kerberos/kerberos_login.ts index 2fe88dc21e5e09..3deb1408dc5c98 100644 --- a/x-pack/test/security_api_integration/tests/kerberos/kerberos_login.ts +++ b/x-pack/test/security_api_integration/tests/kerberos/kerberos_login.ts @@ -8,6 +8,7 @@ import expect from '@kbn/expect'; import request, { Cookie } from 'request'; import { delay } from 'bluebird'; +// @ts-expect-error https://github.com/elastic/kibana/issues/95679 import { adminTestUser } from '@kbn/test'; import { FtrProviderContext } from '../../ftr_provider_context'; import { diff --git a/x-pack/test/security_api_integration/tests/oidc/authorization_code_flow/oidc_auth.ts b/x-pack/test/security_api_integration/tests/oidc/authorization_code_flow/oidc_auth.ts index b2dd65b4f20092..c13ce902ad658e 100644 --- a/x-pack/test/security_api_integration/tests/oidc/authorization_code_flow/oidc_auth.ts +++ b/x-pack/test/security_api_integration/tests/oidc/authorization_code_flow/oidc_auth.ts @@ -9,6 +9,7 @@ import expect from '@kbn/expect'; import request, { Cookie } from 'request'; import url from 'url'; import { delay } from 'bluebird'; +// @ts-expect-error https://github.com/elastic/kibana/issues/95679 import { adminTestUser } from '@kbn/test'; import { getStateAndNonce } from '../../../fixtures/oidc/oidc_tools'; import { FtrProviderContext } from '../../../ftr_provider_context'; diff --git a/x-pack/test/security_api_integration/tests/pki/pki_auth.ts b/x-pack/test/security_api_integration/tests/pki/pki_auth.ts index e3f63aad9e2553..6eca9c354e2483 100644 --- a/x-pack/test/security_api_integration/tests/pki/pki_auth.ts +++ b/x-pack/test/security_api_integration/tests/pki/pki_auth.ts @@ -11,6 +11,7 @@ import { delay } from 'bluebird'; import { readFileSync } from 'fs'; import { resolve } from 'path'; import { CA_CERT_PATH } from '@kbn/dev-utils'; +// @ts-expect-error https://github.com/elastic/kibana/issues/95679 import { adminTestUser } from '@kbn/test'; import { FtrProviderContext } from '../../ftr_provider_context'; diff --git a/x-pack/test/security_api_integration/tests/saml/saml_login.ts b/x-pack/test/security_api_integration/tests/saml/saml_login.ts index e199ba99bfc581..0a76628418c5ef 100644 --- a/x-pack/test/security_api_integration/tests/saml/saml_login.ts +++ b/x-pack/test/security_api_integration/tests/saml/saml_login.ts @@ -10,6 +10,7 @@ import url from 'url'; import { delay } from 'bluebird'; import expect from '@kbn/expect'; import request, { Cookie } from 'request'; +// @ts-expect-error https://github.com/elastic/kibana/issues/95679 import { adminTestUser } from '@kbn/test'; import { getLogoutRequest, diff --git a/x-pack/test/security_api_integration/tests/session_idle/cleanup.ts b/x-pack/test/security_api_integration/tests/session_idle/cleanup.ts index bb46beef41449a..89bb79a4761a09 100644 --- a/x-pack/test/security_api_integration/tests/session_idle/cleanup.ts +++ b/x-pack/test/security_api_integration/tests/session_idle/cleanup.ts @@ -8,6 +8,7 @@ import request, { Cookie } from 'request'; import { delay } from 'bluebird'; import expect from '@kbn/expect'; +// @ts-expect-error https://github.com/elastic/kibana/issues/95679 import { adminTestUser } from '@kbn/test'; import type { AuthenticationProvider } from '../../../../plugins/security/common/model'; import { getSAMLRequestId, getSAMLResponse } from '../../fixtures/saml/saml_tools'; diff --git a/x-pack/test/security_api_integration/tests/session_invalidate/invalidate.ts b/x-pack/test/security_api_integration/tests/session_invalidate/invalidate.ts index 60605c88ce45e7..db41aca86e0ba8 100644 --- a/x-pack/test/security_api_integration/tests/session_invalidate/invalidate.ts +++ b/x-pack/test/security_api_integration/tests/session_invalidate/invalidate.ts @@ -7,6 +7,7 @@ import request, { Cookie } from 'request'; import expect from '@kbn/expect'; +// @ts-expect-error https://github.com/elastic/kibana/issues/95679 import { adminTestUser } from '@kbn/test'; import type { AuthenticationProvider } from '../../../../plugins/security/common/model'; import { getSAMLRequestId, getSAMLResponse } from '../../fixtures/saml/saml_tools'; diff --git a/x-pack/test/security_api_integration/tests/session_lifespan/cleanup.ts b/x-pack/test/security_api_integration/tests/session_lifespan/cleanup.ts index 0b17f037dfbd99..d2419ca07a4345 100644 --- a/x-pack/test/security_api_integration/tests/session_lifespan/cleanup.ts +++ b/x-pack/test/security_api_integration/tests/session_lifespan/cleanup.ts @@ -8,6 +8,7 @@ import request, { Cookie } from 'request'; import { delay } from 'bluebird'; import expect from '@kbn/expect'; +// @ts-expect-error https://github.com/elastic/kibana/issues/95679 import { adminTestUser } from '@kbn/test'; import type { AuthenticationProvider } from '../../../../plugins/security/common/model'; import { getSAMLRequestId, getSAMLResponse } from '../../fixtures/saml/saml_tools'; diff --git a/x-pack/test/security_solution_endpoint/services/endpoint_telemetry.ts b/x-pack/test/security_solution_endpoint/services/endpoint_telemetry.ts index 646e0536c42f17..d91a772ccafacc 100644 --- a/x-pack/test/security_solution_endpoint/services/endpoint_telemetry.ts +++ b/x-pack/test/security_solution_endpoint/services/endpoint_telemetry.ts @@ -7,6 +7,7 @@ import fs from 'fs'; import Path from 'path'; +// @ts-expect-error https://github.com/elastic/kibana/issues/95679 import { KIBANA_ROOT } from '@kbn/test'; import { FtrProviderContext } from '../ftr_provider_context';