diff --git a/.buildkite/scripts/steps/functional/performance_playwright.sh b/.buildkite/scripts/steps/functional/performance_playwright.sh index cdf2e449f7a6..189791cae3e3 100644 --- a/.buildkite/scripts/steps/functional/performance_playwright.sh +++ b/.buildkite/scripts/steps/functional/performance_playwright.sh @@ -46,7 +46,9 @@ unset ELASTIC_APM_SERVER_URL unset ELASTIC_APM_SECRET_TOKEN unset ELASTIC_APM_GLOBAL_LABELS -for journey in x-pack/performance/journeys/*; do +journeys=("login" "ecommerce_dashboard" "flight_dashboard" "web_logs_dashboard" "promotion_tracking_dashboard" "many_fields_discover" "data_stress_test_lens") + +for journey in "${journeys[@]}"; do set +e phases=("WARMUP" "TEST") diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index dc7a6270638c..6eecf19ee57b 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -753,6 +753,9 @@ packages/core/injected-metadata/core-injected-metadata-browser-mocks @elastic/ki packages/core/injected-metadata/core-injected-metadata-common-internal @elastic/kibana-core packages/core/integrations/core-integrations-browser-internal @elastic/kibana-core packages/core/integrations/core-integrations-browser-mocks @elastic/kibana-core +packages/core/lifecycle/core-lifecycle-browser @elastic/kibana-core +packages/core/lifecycle/core-lifecycle-browser-internal @elastic/kibana-core +packages/core/lifecycle/core-lifecycle-browser-mocks @elastic/kibana-core packages/core/logging/core-logging-server @elastic/kibana-core packages/core/logging/core-logging-server-internal @elastic/kibana-core packages/core/logging/core-logging-server-mocks @elastic/kibana-core @@ -772,6 +775,9 @@ packages/core/notifications/core-notifications-browser-mocks @elastic/kibana-cor packages/core/overlays/core-overlays-browser @elastic/kibana-core packages/core/overlays/core-overlays-browser-internal @elastic/kibana-core packages/core/overlays/core-overlays-browser-mocks @elastic/kibana-core +packages/core/plugins/core-plugins-browser @elastic/kibana-core +packages/core/plugins/core-plugins-browser-internal @elastic/kibana-core +packages/core/plugins/core-plugins-browser-mocks @elastic/kibana-core packages/core/preboot/core-preboot-server @elastic/kibana-core packages/core/preboot/core-preboot-server-internal @elastic/kibana-core packages/core/preboot/core-preboot-server-mocks @elastic/kibana-core diff --git a/docs/api/saved-objects.asciidoc b/docs/api/saved-objects.asciidoc index 6cabbdc785db..610f18c38d62 100644 --- a/docs/api/saved-objects.asciidoc +++ b/docs/api/saved-objects.asciidoc @@ -30,6 +30,8 @@ The following saved objects APIs are available: * <> to remove {kib} saved objects +* <> to remove multiple {kib} saved objects + * <> to retrieve sets of saved objects that you want to import into {kib} * <> to create sets of {kib} saved objects from a file created by the export API @@ -46,6 +48,7 @@ include::saved-objects/bulk_create.asciidoc[] include::saved-objects/update.asciidoc[] include::saved-objects/bulk_update.asciidoc[] include::saved-objects/delete.asciidoc[] +include::saved-objects/bulk_delete.asciidoc[] include::saved-objects/export.asciidoc[] include::saved-objects/import.asciidoc[] include::saved-objects/resolve_import_errors.asciidoc[] diff --git a/docs/api/saved-objects/bulk_delete.asciidoc b/docs/api/saved-objects/bulk_delete.asciidoc new file mode 100644 index 000000000000..d08c5874477b --- /dev/null +++ b/docs/api/saved-objects/bulk_delete.asciidoc @@ -0,0 +1,107 @@ +[[saved-objects-api-bulk-delete]] +=== Bulk delete object API +++++ +Bulk delete objects +++++ + +experimental[] Remove multiple {kib} saved objects. + +WARNING: Once you delete a saved object, _it cannot be recovered_. + +==== Request + +`POST :/api/saved_objects/_bulk_delete` + +`POST :/s//api/saved_objects/_bulk_delete` + +==== 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. + +==== Request body + +`type`:: + (Required, string) Valid options include `visualization`, `dashboard`, `search`, `index-pattern`, `config`. + +`id`:: + (Required, string) The object ID to remove. + +==== Query parameters + +`force`:: + (Optional, boolean) When true, force delete objects that exist in multiple namespaces. Note that the option applies to the whole request. Use the <> to specify per-object delete behavior. ++ +TIP: Use this if you attempted to delete objects and received an HTTP 400 error with the following message: _"Unable to delete saved object that exists in multiple namespaces, use the `force` option to delete it anyway"_ ++ +WARNING: When you bulk delete objects that exist in multiple namespaces, the API also deletes <> that reference the object. These requests are batched to minimise the impact but they can place a heavy load on {kib}. Make sure you limit the number of objects that exist in multiple namespaces in a single bulk delete operation. + +==== Response code +`200`:: + Indicates a successful call. Note, this HTTP response code indicates that the bulk operation succeeded. Errors pertaining to individual + objects will be returned in the response body. Refer to the example below for details. + +==== Response body + +`statuses`:: + (array) Top-level property that contains objects that represent the response for each of the requested objects. The order of the objects in the response is identical to the order of the objects in the request. + +Saved objects that cannot be removed will include an error object. + +==== Example + +Delete three saved objects, where one of them does not exist and one exists in multiple namespaces: + +[source,sh] +-------------------------------------------------- +$ curl -X POST api/saved_objects/_bulk_delete +[ + { + type: 'visualization', + id: 'not an id', + }, + { + type: 'dashboard', + id: 'be3733a0-9efe-11e7-acb3-3dab96693fab', + { + type: 'index-pattern', + id: 'd3d7af60-4c81-11e8-b3d7-01146121b73d', + } +] +-------------------------------------------------- +// KIBANA + +The API returns the following: + +[source,sh] +-------------------------------------------------- +{ + "statuses": [ + { + "success": false, + "id": "not an id", + "type": "visualization", + "error": { + "statusCode": 404, + "error": "Not Found", + "message": "Saved object [visualization/not an id] not found", + }, + }, + { + "success": true, + "id": "be3733a0-9efe-11e7-acb3-3dab96693fab", + "type": "dashboard", + }, + { + "success": false, + "id": "d3d7af60-4c81-11e8-b3d7-01146121b73d", + "type": "index-pattern", + "error": { + "statusCode": 400, + "error": "Bad Request", + "message": "Unable to delete saved object id: d3d7af60-4c81-11e8-b3d7-01146121b73d, type: index-pattern that exists in multiple namespaces, use the \"force\" option to delete all saved objects: Bad Request", + }, + } + ] +} +-------------------------------------------------- diff --git a/package.json b/package.json index 7c8a27447d4c..ff83389b8f3f 100644 --- a/package.json +++ b/package.json @@ -230,6 +230,9 @@ "@kbn/core-injected-metadata-common-internal": "link:bazel-bin/packages/core/injected-metadata/core-injected-metadata-common-internal", "@kbn/core-integrations-browser-internal": "link:bazel-bin/packages/core/integrations/core-integrations-browser-internal", "@kbn/core-integrations-browser-mocks": "link:bazel-bin/packages/core/integrations/core-integrations-browser-mocks", + "@kbn/core-lifecycle-browser": "link:bazel-bin/packages/core/lifecycle/core-lifecycle-browser", + "@kbn/core-lifecycle-browser-internal": "link:bazel-bin/packages/core/lifecycle/core-lifecycle-browser-internal", + "@kbn/core-lifecycle-browser-mocks": "link:bazel-bin/packages/core/lifecycle/core-lifecycle-browser-mocks", "@kbn/core-logging-server": "link:bazel-bin/packages/core/logging/core-logging-server", "@kbn/core-logging-server-internal": "link:bazel-bin/packages/core/logging/core-logging-server-internal", "@kbn/core-logging-server-mocks": "link:bazel-bin/packages/core/logging/core-logging-server-mocks", @@ -249,6 +252,9 @@ "@kbn/core-overlays-browser": "link:bazel-bin/packages/core/overlays/core-overlays-browser", "@kbn/core-overlays-browser-internal": "link:bazel-bin/packages/core/overlays/core-overlays-browser-internal", "@kbn/core-overlays-browser-mocks": "link:bazel-bin/packages/core/overlays/core-overlays-browser-mocks", + "@kbn/core-plugins-browser": "link:bazel-bin/packages/core/plugins/core-plugins-browser", + "@kbn/core-plugins-browser-internal": "link:bazel-bin/packages/core/plugins/core-plugins-browser-internal", + "@kbn/core-plugins-browser-mocks": "link:bazel-bin/packages/core/plugins/core-plugins-browser-mocks", "@kbn/core-preboot-server": "link:bazel-bin/packages/core/preboot/core-preboot-server", "@kbn/core-preboot-server-internal": "link:bazel-bin/packages/core/preboot/core-preboot-server-internal", "@kbn/core-preboot-server-mocks": "link:bazel-bin/packages/core/preboot/core-preboot-server-mocks", @@ -940,6 +946,9 @@ "@types/kbn__core-injected-metadata-common-internal": "link:bazel-bin/packages/core/injected-metadata/core-injected-metadata-common-internal/npm_module_types", "@types/kbn__core-integrations-browser-internal": "link:bazel-bin/packages/core/integrations/core-integrations-browser-internal/npm_module_types", "@types/kbn__core-integrations-browser-mocks": "link:bazel-bin/packages/core/integrations/core-integrations-browser-mocks/npm_module_types", + "@types/kbn__core-lifecycle-browser": "link:bazel-bin/packages/core/lifecycle/core-lifecycle-browser/npm_module_types", + "@types/kbn__core-lifecycle-browser-internal": "link:bazel-bin/packages/core/lifecycle/core-lifecycle-browser-internal/npm_module_types", + "@types/kbn__core-lifecycle-browser-mocks": "link:bazel-bin/packages/core/lifecycle/core-lifecycle-browser-mocks/npm_module_types", "@types/kbn__core-logging-server": "link:bazel-bin/packages/core/logging/core-logging-server/npm_module_types", "@types/kbn__core-logging-server-internal": "link:bazel-bin/packages/core/logging/core-logging-server-internal/npm_module_types", "@types/kbn__core-logging-server-mocks": "link:bazel-bin/packages/core/logging/core-logging-server-mocks/npm_module_types", @@ -959,6 +968,9 @@ "@types/kbn__core-overlays-browser": "link:bazel-bin/packages/core/overlays/core-overlays-browser/npm_module_types", "@types/kbn__core-overlays-browser-internal": "link:bazel-bin/packages/core/overlays/core-overlays-browser-internal/npm_module_types", "@types/kbn__core-overlays-browser-mocks": "link:bazel-bin/packages/core/overlays/core-overlays-browser-mocks/npm_module_types", + "@types/kbn__core-plugins-browser": "link:bazel-bin/packages/core/plugins/core-plugins-browser/npm_module_types", + "@types/kbn__core-plugins-browser-internal": "link:bazel-bin/packages/core/plugins/core-plugins-browser-internal/npm_module_types", + "@types/kbn__core-plugins-browser-mocks": "link:bazel-bin/packages/core/plugins/core-plugins-browser-mocks/npm_module_types", "@types/kbn__core-preboot-server": "link:bazel-bin/packages/core/preboot/core-preboot-server/npm_module_types", "@types/kbn__core-preboot-server-internal": "link:bazel-bin/packages/core/preboot/core-preboot-server-internal/npm_module_types", "@types/kbn__core-preboot-server-mocks": "link:bazel-bin/packages/core/preboot/core-preboot-server-mocks/npm_module_types", diff --git a/packages/BUILD.bazel b/packages/BUILD.bazel index d6994772c9ef..ea24a3a04602 100644 --- a/packages/BUILD.bazel +++ b/packages/BUILD.bazel @@ -96,6 +96,9 @@ filegroup( "//packages/core/injected-metadata/core-injected-metadata-common-internal:build", "//packages/core/integrations/core-integrations-browser-internal:build", "//packages/core/integrations/core-integrations-browser-mocks:build", + "//packages/core/lifecycle/core-lifecycle-browser:build", + "//packages/core/lifecycle/core-lifecycle-browser-internal:build", + "//packages/core/lifecycle/core-lifecycle-browser-mocks:build", "//packages/core/logging/core-logging-server:build", "//packages/core/logging/core-logging-server-internal:build", "//packages/core/logging/core-logging-server-mocks:build", @@ -115,6 +118,9 @@ filegroup( "//packages/core/overlays/core-overlays-browser:build", "//packages/core/overlays/core-overlays-browser-internal:build", "//packages/core/overlays/core-overlays-browser-mocks:build", + "//packages/core/plugins/core-plugins-browser:build", + "//packages/core/plugins/core-plugins-browser-internal:build", + "//packages/core/plugins/core-plugins-browser-mocks:build", "//packages/core/preboot/core-preboot-server:build", "//packages/core/preboot/core-preboot-server-internal:build", "//packages/core/preboot/core-preboot-server-mocks:build", @@ -419,6 +425,9 @@ filegroup( "//packages/core/injected-metadata/core-injected-metadata-common-internal:build_types", "//packages/core/integrations/core-integrations-browser-internal:build_types", "//packages/core/integrations/core-integrations-browser-mocks:build_types", + "//packages/core/lifecycle/core-lifecycle-browser:build_types", + "//packages/core/lifecycle/core-lifecycle-browser-internal:build_types", + "//packages/core/lifecycle/core-lifecycle-browser-mocks:build_types", "//packages/core/logging/core-logging-server:build_types", "//packages/core/logging/core-logging-server-internal:build_types", "//packages/core/logging/core-logging-server-mocks:build_types", @@ -438,6 +447,9 @@ filegroup( "//packages/core/overlays/core-overlays-browser:build_types", "//packages/core/overlays/core-overlays-browser-internal:build_types", "//packages/core/overlays/core-overlays-browser-mocks:build_types", + "//packages/core/plugins/core-plugins-browser:build_types", + "//packages/core/plugins/core-plugins-browser-internal:build_types", + "//packages/core/plugins/core-plugins-browser-mocks:build_types", "//packages/core/preboot/core-preboot-server:build_types", "//packages/core/preboot/core-preboot-server-internal:build_types", "//packages/core/preboot/core-preboot-server-mocks:build_types", diff --git a/packages/core/lifecycle/core-lifecycle-browser-internal/BUILD.bazel b/packages/core/lifecycle/core-lifecycle-browser-internal/BUILD.bazel new file mode 100644 index 000000000000..3f1aa3eb50c4 --- /dev/null +++ b/packages/core/lifecycle/core-lifecycle-browser-internal/BUILD.bazel @@ -0,0 +1,116 @@ +load("@npm//@bazel/typescript:index.bzl", "ts_config") +load("@build_bazel_rules_nodejs//:index.bzl", "js_library") +load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") + +PKG_DIRNAME = "core-lifecycle-browser-internal" +PKG_REQUIRE_NAME = "@kbn/core-lifecycle-browser-internal" + +SOURCE_FILES = glob( + [ + "**/*.ts", + "**/*.tsx", + ], + exclude = [ + "**/*.config.js", + "**/*.mock.*", + "**/*.test.*", + "**/*.stories.*", + "**/__snapshots__/**", + "**/integration_tests/**", + "**/mocks/**", + "**/scripts/**", + "**/storybook/**", + "**/test_fixtures/**", + "**/test_helpers/**", + ], +) + +SRCS = SOURCE_FILES + +filegroup( + name = "srcs", + srcs = SRCS, +) + +NPM_MODULE_EXTRA_FILES = [ + "package.json", +] + +RUNTIME_DEPS = [ +] + +TYPES_DEPS = [ + "@npm//@types/node", + "@npm//@types/jest", + "//packages/core/lifecycle/core-lifecycle-browser:npm_module_types", + "//packages/core/application/core-application-browser-internal:npm_module_types", + "//packages/core/injected-metadata/core-injected-metadata-browser-internal:npm_module_types", +] + +jsts_transpiler( + name = "target_node", + srcs = SRCS, + build_pkg_name = package_name(), +) + +jsts_transpiler( + name = "target_web", + srcs = SRCS, + build_pkg_name = package_name(), + web = True, +) + +ts_config( + name = "tsconfig", + src = "tsconfig.json", + deps = [ + "//:tsconfig.base.json", + "//:tsconfig.bazel.json", + ], +) + +ts_project( + name = "tsc_types", + args = ['--pretty'], + srcs = SRCS, + deps = TYPES_DEPS, + declaration = True, + declaration_map = True, + emit_declaration_only = True, + out_dir = "target_types", + tsconfig = ":tsconfig", +) + +js_library( + name = PKG_DIRNAME, + srcs = NPM_MODULE_EXTRA_FILES, + deps = RUNTIME_DEPS + [":target_node", ":target_web"], + package_name = PKG_REQUIRE_NAME, + visibility = ["//visibility:public"], +) + +pkg_npm( + name = "npm_module", + deps = [":" + PKG_DIRNAME], +) + +filegroup( + name = "build", + srcs = [":npm_module"], + visibility = ["//visibility:public"], +) + +pkg_npm_types( + name = "npm_module_types", + srcs = SRCS, + deps = [":tsc_types"], + package_name = PKG_REQUIRE_NAME, + tsconfig = ":tsconfig", + visibility = ["//visibility:public"], +) + +filegroup( + name = "build_types", + srcs = [":npm_module_types"], + visibility = ["//visibility:public"], +) diff --git a/packages/core/lifecycle/core-lifecycle-browser-internal/README.md b/packages/core/lifecycle/core-lifecycle-browser-internal/README.md new file mode 100644 index 000000000000..e25f622ea619 --- /dev/null +++ b/packages/core/lifecycle/core-lifecycle-browser-internal/README.md @@ -0,0 +1,5 @@ +# @kbn/core-lifecycle-browser-internal + +This package contains the internal types for core's lifecycle contracts: +- `InternalCoreSetup` +- `InternalCoreStart` diff --git a/packages/core/lifecycle/core-lifecycle-browser-internal/index.ts b/packages/core/lifecycle/core-lifecycle-browser-internal/index.ts new file mode 100644 index 000000000000..9cb96150c7f2 --- /dev/null +++ b/packages/core/lifecycle/core-lifecycle-browser-internal/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 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 { InternalCoreSetup, InternalCoreStart } from './src'; diff --git a/packages/core/lifecycle/core-lifecycle-browser-internal/jest.config.js b/packages/core/lifecycle/core-lifecycle-browser-internal/jest.config.js new file mode 100644 index 000000000000..713613cbc6d1 --- /dev/null +++ b/packages/core/lifecycle/core-lifecycle-browser-internal/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/core/lifecycle/core-lifecycle-browser-internal'], +}; diff --git a/packages/core/lifecycle/core-lifecycle-browser-internal/kibana.jsonc b/packages/core/lifecycle/core-lifecycle-browser-internal/kibana.jsonc new file mode 100644 index 000000000000..c552d622aa43 --- /dev/null +++ b/packages/core/lifecycle/core-lifecycle-browser-internal/kibana.jsonc @@ -0,0 +1,7 @@ +{ + "type": "shared-common", + "id": "@kbn/core-lifecycle-browser-internal", + "owner": "@elastic/kibana-core", + "runtimeDeps": [], + "typeDeps": [], +} diff --git a/packages/core/lifecycle/core-lifecycle-browser-internal/package.json b/packages/core/lifecycle/core-lifecycle-browser-internal/package.json new file mode 100644 index 000000000000..738d3fed2bb5 --- /dev/null +++ b/packages/core/lifecycle/core-lifecycle-browser-internal/package.json @@ -0,0 +1,9 @@ +{ + "name": "@kbn/core-lifecycle-browser-internal", + "private": true, + "version": "1.0.0", + "main": "./target_node/index.js", + "browser": "./target_web/index.js", + "author": "Kibana Core", + "license": "SSPL-1.0 OR Elastic License 2.0" +} diff --git a/packages/core/lifecycle/core-lifecycle-browser-internal/src/index.ts b/packages/core/lifecycle/core-lifecycle-browser-internal/src/index.ts new file mode 100644 index 000000000000..fdd37f693fee --- /dev/null +++ b/packages/core/lifecycle/core-lifecycle-browser-internal/src/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 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 { InternalCoreSetup } from './internal_core_setup'; +export type { InternalCoreStart } from './internal_core_start'; diff --git a/packages/core/lifecycle/core-lifecycle-browser-internal/src/internal_core_setup.ts b/packages/core/lifecycle/core-lifecycle-browser-internal/src/internal_core_setup.ts new file mode 100644 index 000000000000..e844b3facf76 --- /dev/null +++ b/packages/core/lifecycle/core-lifecycle-browser-internal/src/internal_core_setup.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 { CoreSetup } from '@kbn/core-lifecycle-browser'; +import type { InternalApplicationSetup } from '@kbn/core-application-browser-internal'; +import type { InternalInjectedMetadataSetup } from '@kbn/core-injected-metadata-browser-internal'; + +/** @internal */ +export interface InternalCoreSetup extends Omit { + application: InternalApplicationSetup; + injectedMetadata: InternalInjectedMetadataSetup; +} diff --git a/packages/core/lifecycle/core-lifecycle-browser-internal/src/internal_core_start.ts b/packages/core/lifecycle/core-lifecycle-browser-internal/src/internal_core_start.ts new file mode 100644 index 000000000000..294c1b226b73 --- /dev/null +++ b/packages/core/lifecycle/core-lifecycle-browser-internal/src/internal_core_start.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 { CoreStart } from '@kbn/core-lifecycle-browser'; +import type { InternalApplicationStart } from '@kbn/core-application-browser-internal'; +import type { InternalInjectedMetadataStart } from '@kbn/core-injected-metadata-browser-internal'; + +/** @internal */ +export interface InternalCoreStart extends Omit { + application: InternalApplicationStart; + injectedMetadata: InternalInjectedMetadataStart; +} diff --git a/packages/core/lifecycle/core-lifecycle-browser-internal/tsconfig.json b/packages/core/lifecycle/core-lifecycle-browser-internal/tsconfig.json new file mode 100644 index 000000000000..62f956eb463d --- /dev/null +++ b/packages/core/lifecycle/core-lifecycle-browser-internal/tsconfig.json @@ -0,0 +1,18 @@ +{ + "extends": "../../../../tsconfig.bazel.json", + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "emitDeclarationOnly": true, + "outDir": "target_types", + "stripInternal": false, + "types": [ + "jest", + "node", + ] + }, + "include": [ + "**/*.ts", + "**/*.tsx", + ] +} diff --git a/packages/core/lifecycle/core-lifecycle-browser-mocks/BUILD.bazel b/packages/core/lifecycle/core-lifecycle-browser-mocks/BUILD.bazel new file mode 100644 index 000000000000..eaf6de1d6576 --- /dev/null +++ b/packages/core/lifecycle/core-lifecycle-browser-mocks/BUILD.bazel @@ -0,0 +1,142 @@ +load("@npm//@bazel/typescript:index.bzl", "ts_config") +load("@build_bazel_rules_nodejs//:index.bzl", "js_library") +load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") + +PKG_DIRNAME = "core-lifecycle-browser-mocks" +PKG_REQUIRE_NAME = "@kbn/core-lifecycle-browser-mocks" + +SOURCE_FILES = glob( + [ + "**/*.ts", + "**/*.tsx", + ], + exclude = [ + "**/*.config.js", + "**/*.test.*", + "**/*.stories.*", + "**/__snapshots__/**", + "**/integration_tests/**", + "**/mocks/**", + "**/scripts/**", + "**/storybook/**", + "**/test_fixtures/**", + "**/test_helpers/**", + ], +) + +SRCS = SOURCE_FILES + +filegroup( + name = "srcs", + srcs = SRCS, +) + +NPM_MODULE_EXTRA_FILES = [ + "package.json", +] + +RUNTIME_DEPS = [ + "//packages/core/injected-metadata/core-injected-metadata-browser-mocks", + "//packages/core/doc-links/core-doc-links-browser-mocks", + "//packages/core/theme/core-theme-browser-mocks", + "//packages/core/analytics/core-analytics-browser-mocks", + "//packages/core/execution-context/core-execution-context-browser-mocks", + "//packages/core/i18n/core-i18n-browser-mocks", + "//packages/core/fatal-errors/core-fatal-errors-browser-mocks", + "//packages/core/http/core-http-browser-mocks", + "//packages/core/ui-settings/core-ui-settings-browser-mocks", + "//packages/core/deprecations/core-deprecations-browser-mocks", + "//packages/core/overlays/core-overlays-browser-mocks", + "//packages/core/saved-objects/core-saved-objects-browser-mocks", + "//packages/core/notifications/core-notifications-browser-mocks", + "//packages/core/application/core-application-browser-mocks", + "//packages/core/chrome/core-chrome-browser-mocks", +] + +TYPES_DEPS = [ + "@npm//@types/node", + "@npm//@types/jest", + "//packages/core/injected-metadata/core-injected-metadata-browser-mocks:npm_module_types", + "//packages/core/doc-links/core-doc-links-browser-mocks:npm_module_types", + "//packages/core/theme/core-theme-browser-mocks:npm_module_types", + "//packages/core/analytics/core-analytics-browser-mocks:npm_module_types", + "//packages/core/execution-context/core-execution-context-browser-mocks:npm_module_types", + "//packages/core/i18n/core-i18n-browser-mocks:npm_module_types", + "//packages/core/fatal-errors/core-fatal-errors-browser-mocks:npm_module_types", + "//packages/core/http/core-http-browser-mocks:npm_module_types", + "//packages/core/ui-settings/core-ui-settings-browser-mocks:npm_module_types", + "//packages/core/deprecations/core-deprecations-browser-mocks:npm_module_types", + "//packages/core/overlays/core-overlays-browser-mocks:npm_module_types", + "//packages/core/saved-objects/core-saved-objects-browser-mocks:npm_module_types", + "//packages/core/notifications/core-notifications-browser-mocks:npm_module_types", + "//packages/core/application/core-application-browser-mocks:npm_module_types", + "//packages/core/chrome/core-chrome-browser-mocks:npm_module_types", +] + +jsts_transpiler( + name = "target_node", + srcs = SRCS, + build_pkg_name = package_name(), +) + +jsts_transpiler( + name = "target_web", + srcs = SRCS, + build_pkg_name = package_name(), + web = True, +) + +ts_config( + name = "tsconfig", + src = "tsconfig.json", + deps = [ + "//:tsconfig.base.json", + "//:tsconfig.bazel.json", + ], +) + +ts_project( + name = "tsc_types", + args = ['--pretty'], + srcs = SRCS, + deps = TYPES_DEPS, + declaration = True, + declaration_map = True, + emit_declaration_only = True, + out_dir = "target_types", + tsconfig = ":tsconfig", +) + +js_library( + name = PKG_DIRNAME, + srcs = NPM_MODULE_EXTRA_FILES, + deps = RUNTIME_DEPS + [":target_node", ":target_web"], + package_name = PKG_REQUIRE_NAME, + visibility = ["//visibility:public"], +) + +pkg_npm( + name = "npm_module", + deps = [":" + PKG_DIRNAME], +) + +filegroup( + name = "build", + srcs = [":npm_module"], + visibility = ["//visibility:public"], +) + +pkg_npm_types( + name = "npm_module_types", + srcs = SRCS, + deps = [":tsc_types"], + package_name = PKG_REQUIRE_NAME, + tsconfig = ":tsconfig", + visibility = ["//visibility:public"], +) + +filegroup( + name = "build_types", + srcs = [":npm_module_types"], + visibility = ["//visibility:public"], +) diff --git a/packages/core/lifecycle/core-lifecycle-browser-mocks/README.md b/packages/core/lifecycle/core-lifecycle-browser-mocks/README.md new file mode 100644 index 000000000000..c250bfc7fa02 --- /dev/null +++ b/packages/core/lifecycle/core-lifecycle-browser-mocks/README.md @@ -0,0 +1,4 @@ +# @kbn/core-lifecycle-browser-mocks + +This package contains the mocks for core's lifecycle contracts: +- `coreLifecycleMock` \ No newline at end of file diff --git a/packages/core/lifecycle/core-lifecycle-browser-mocks/index.ts b/packages/core/lifecycle/core-lifecycle-browser-mocks/index.ts new file mode 100644 index 000000000000..b748940720b2 --- /dev/null +++ b/packages/core/lifecycle/core-lifecycle-browser-mocks/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 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 { coreLifecycleMock } from './src'; diff --git a/packages/core/lifecycle/core-lifecycle-browser-mocks/jest.config.js b/packages/core/lifecycle/core-lifecycle-browser-mocks/jest.config.js new file mode 100644 index 000000000000..1568be8604fe --- /dev/null +++ b/packages/core/lifecycle/core-lifecycle-browser-mocks/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/core/lifecycle/core-lifecycle-browser-mocks'], +}; diff --git a/packages/core/lifecycle/core-lifecycle-browser-mocks/kibana.jsonc b/packages/core/lifecycle/core-lifecycle-browser-mocks/kibana.jsonc new file mode 100644 index 000000000000..ed65ce8dacf5 --- /dev/null +++ b/packages/core/lifecycle/core-lifecycle-browser-mocks/kibana.jsonc @@ -0,0 +1,7 @@ +{ + "type": "shared-common", + "id": "@kbn/core-lifecycle-browser-mocks", + "owner": "@elastic/kibana-core", + "runtimeDeps": [], + "typeDeps": [], +} diff --git a/packages/core/lifecycle/core-lifecycle-browser-mocks/package.json b/packages/core/lifecycle/core-lifecycle-browser-mocks/package.json new file mode 100644 index 000000000000..fd1224d4d078 --- /dev/null +++ b/packages/core/lifecycle/core-lifecycle-browser-mocks/package.json @@ -0,0 +1,9 @@ +{ + "name": "@kbn/core-lifecycle-browser-mocks", + "private": true, + "version": "1.0.0", + "main": "./target_node/index.js", + "browser": "./target_web/index.js", + "author": "Kibana Core", + "license": "SSPL-1.0 OR Elastic License 2.0" +} diff --git a/packages/core/lifecycle/core-lifecycle-browser-mocks/src/core_setup.mock.ts b/packages/core/lifecycle/core-lifecycle-browser-mocks/src/core_setup.mock.ts new file mode 100644 index 000000000000..b9877891ada9 --- /dev/null +++ b/packages/core/lifecycle/core-lifecycle-browser-mocks/src/core_setup.mock.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 { injectedMetadataServiceMock } from '@kbn/core-injected-metadata-browser-mocks'; +import { docLinksServiceMock } from '@kbn/core-doc-links-browser-mocks'; +import { themeServiceMock } from '@kbn/core-theme-browser-mocks'; +import { analyticsServiceMock } from '@kbn/core-analytics-browser-mocks'; +import { executionContextServiceMock } from '@kbn/core-execution-context-browser-mocks'; +import { fatalErrorsServiceMock } from '@kbn/core-fatal-errors-browser-mocks'; +import { httpServiceMock } from '@kbn/core-http-browser-mocks'; +import { uiSettingsServiceMock } from '@kbn/core-ui-settings-browser-mocks'; +import { deprecationsServiceMock } from '@kbn/core-deprecations-browser-mocks'; +import { notificationServiceMock } from '@kbn/core-notifications-browser-mocks'; +import { applicationServiceMock } from '@kbn/core-application-browser-mocks'; +import { createCoreStartMock } from './core_start.mock'; + +export function createCoreSetupMock({ + basePath = '', + pluginStartDeps = {}, + pluginStartContract, +}: { + basePath?: string; + pluginStartDeps?: object; + pluginStartContract?: any; +} = {}) { + const mock = { + analytics: analyticsServiceMock.createAnalyticsServiceSetup(), + application: applicationServiceMock.createSetupContract(), + docLinks: docLinksServiceMock.createSetupContract(), + executionContext: executionContextServiceMock.createSetupContract(), + fatalErrors: fatalErrorsServiceMock.createSetupContract(), + getStartServices: jest.fn, any, any]>, []>(() => + Promise.resolve([createCoreStartMock({ basePath }), pluginStartDeps, pluginStartContract]) + ), + http: httpServiceMock.createSetupContract({ basePath }), + notifications: notificationServiceMock.createSetupContract(), + uiSettings: uiSettingsServiceMock.createSetupContract(), + deprecations: deprecationsServiceMock.createSetupContract(), + injectedMetadata: { + getInjectedVar: injectedMetadataServiceMock.createSetupContract().getInjectedVar, + }, + theme: themeServiceMock.createSetupContract(), + }; + + return mock; +} diff --git a/packages/core/lifecycle/core-lifecycle-browser-mocks/src/core_start.mock.ts b/packages/core/lifecycle/core-lifecycle-browser-mocks/src/core_start.mock.ts new file mode 100644 index 000000000000..4fa223e05173 --- /dev/null +++ b/packages/core/lifecycle/core-lifecycle-browser-mocks/src/core_start.mock.ts @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 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 { injectedMetadataServiceMock } from '@kbn/core-injected-metadata-browser-mocks'; +import { docLinksServiceMock } from '@kbn/core-doc-links-browser-mocks'; +import { themeServiceMock } from '@kbn/core-theme-browser-mocks'; +import { analyticsServiceMock } from '@kbn/core-analytics-browser-mocks'; +import { executionContextServiceMock } from '@kbn/core-execution-context-browser-mocks'; +import { i18nServiceMock } from '@kbn/core-i18n-browser-mocks'; +import { fatalErrorsServiceMock } from '@kbn/core-fatal-errors-browser-mocks'; +import { httpServiceMock } from '@kbn/core-http-browser-mocks'; +import { uiSettingsServiceMock } from '@kbn/core-ui-settings-browser-mocks'; +import { deprecationsServiceMock } from '@kbn/core-deprecations-browser-mocks'; +import { overlayServiceMock } from '@kbn/core-overlays-browser-mocks'; +import { savedObjectsServiceMock } from '@kbn/core-saved-objects-browser-mocks'; +import { notificationServiceMock } from '@kbn/core-notifications-browser-mocks'; +import { applicationServiceMock } from '@kbn/core-application-browser-mocks'; +import { chromeServiceMock } from '@kbn/core-chrome-browser-mocks'; + +export function createCoreStartMock({ basePath = '' } = {}) { + const mock = { + analytics: analyticsServiceMock.createAnalyticsServiceStart(), + application: applicationServiceMock.createStartContract(), + chrome: chromeServiceMock.createStartContract(), + docLinks: docLinksServiceMock.createStartContract(), + executionContext: executionContextServiceMock.createStartContract(), + http: httpServiceMock.createStartContract({ basePath }), + i18n: i18nServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + overlays: overlayServiceMock.createStartContract(), + uiSettings: uiSettingsServiceMock.createStartContract(), + savedObjects: savedObjectsServiceMock.createStartContract(), + deprecations: deprecationsServiceMock.createStartContract(), + theme: themeServiceMock.createStartContract(), + injectedMetadata: { + getInjectedVar: injectedMetadataServiceMock.createStartContract().getInjectedVar, + }, + fatalErrors: fatalErrorsServiceMock.createStartContract(), + }; + + return mock; +} diff --git a/packages/core/lifecycle/core-lifecycle-browser-mocks/src/index.ts b/packages/core/lifecycle/core-lifecycle-browser-mocks/src/index.ts new file mode 100644 index 000000000000..6d55549b4345 --- /dev/null +++ b/packages/core/lifecycle/core-lifecycle-browser-mocks/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. + */ + +import { createCoreSetupMock } from './core_setup.mock'; +import { createCoreStartMock } from './core_start.mock'; + +export const coreLifecycleMock = { + createCoreSetup: createCoreSetupMock, + createCoreStart: createCoreStartMock, +}; diff --git a/packages/core/lifecycle/core-lifecycle-browser-mocks/tsconfig.json b/packages/core/lifecycle/core-lifecycle-browser-mocks/tsconfig.json new file mode 100644 index 000000000000..4283cbe1b760 --- /dev/null +++ b/packages/core/lifecycle/core-lifecycle-browser-mocks/tsconfig.json @@ -0,0 +1,18 @@ +{ + "extends": "../../../../tsconfig.bazel.json", + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "emitDeclarationOnly": true, + "outDir": "target_types", + "stripInternal": false, + "types": [ + "jest", + "node", + ] + }, + "include": [ + "**/*.ts", + "**/*.tsx", + ] +} diff --git a/packages/core/lifecycle/core-lifecycle-browser/BUILD.bazel b/packages/core/lifecycle/core-lifecycle-browser/BUILD.bazel new file mode 100644 index 000000000000..d32c12c10728 --- /dev/null +++ b/packages/core/lifecycle/core-lifecycle-browser/BUILD.bazel @@ -0,0 +1,128 @@ +load("@npm//@bazel/typescript:index.bzl", "ts_config") +load("@build_bazel_rules_nodejs//:index.bzl", "js_library") +load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") + +PKG_DIRNAME = "core-lifecycle-browser" +PKG_REQUIRE_NAME = "@kbn/core-lifecycle-browser" + +SOURCE_FILES = glob( + [ + "**/*.ts", + "**/*.tsx", + ], + exclude = [ + "**/*.config.js", + "**/*.mock.*", + "**/*.test.*", + "**/*.stories.*", + "**/__snapshots__/**", + "**/integration_tests/**", + "**/mocks/**", + "**/scripts/**", + "**/storybook/**", + "**/test_fixtures/**", + "**/test_helpers/**", + ], +) + +SRCS = SOURCE_FILES + +filegroup( + name = "srcs", + srcs = SRCS, +) + +NPM_MODULE_EXTRA_FILES = [ + "package.json", +] + +RUNTIME_DEPS = [ +] + +TYPES_DEPS = [ + "@npm//@types/node", + "@npm//@types/jest", + "//packages/core/injected-metadata/core-injected-metadata-browser:npm_module_types", + "//packages/core/theme/core-theme-browser:npm_module_types", + "//packages/core/analytics/core-analytics-browser:npm_module_types", + "//packages/core/execution-context/core-execution-context-browser:npm_module_types", + "//packages/core/http/core-http-browser:npm_module_types", + "//packages/core/fatal-errors/core-fatal-errors-browser:npm_module_types", + "//packages/core/ui-settings/core-ui-settings-browser:npm_module_types", + "//packages/core/notifications/core-notifications-browser:npm_module_types", + "//packages/core/application/core-application-browser:npm_module_types", + "//packages/core/doc-links/core-doc-links-browser:npm_module_types", + "//packages/core/i18n/core-i18n-browser:npm_module_types", + "//packages/core/deprecations/core-deprecations-browser:npm_module_types", + "//packages/core/overlays/core-overlays-browser:npm_module_types", + "//packages/core/saved-objects/core-saved-objects-browser:npm_module_types", + "//packages/core/chrome/core-chrome-browser:npm_module_types", +] + +jsts_transpiler( + name = "target_node", + srcs = SRCS, + build_pkg_name = package_name(), +) + +jsts_transpiler( + name = "target_web", + srcs = SRCS, + build_pkg_name = package_name(), + web = True, +) + +ts_config( + name = "tsconfig", + src = "tsconfig.json", + deps = [ + "//:tsconfig.base.json", + "//:tsconfig.bazel.json", + ], +) + +ts_project( + name = "tsc_types", + args = ['--pretty'], + srcs = SRCS, + deps = TYPES_DEPS, + declaration = True, + declaration_map = True, + emit_declaration_only = True, + out_dir = "target_types", + tsconfig = ":tsconfig", +) + +js_library( + name = PKG_DIRNAME, + srcs = NPM_MODULE_EXTRA_FILES, + deps = RUNTIME_DEPS + [":target_node", ":target_web"], + package_name = PKG_REQUIRE_NAME, + visibility = ["//visibility:public"], +) + +pkg_npm( + name = "npm_module", + deps = [":" + PKG_DIRNAME], +) + +filegroup( + name = "build", + srcs = [":npm_module"], + visibility = ["//visibility:public"], +) + +pkg_npm_types( + name = "npm_module_types", + srcs = SRCS, + deps = [":tsc_types"], + package_name = PKG_REQUIRE_NAME, + tsconfig = ":tsconfig", + visibility = ["//visibility:public"], +) + +filegroup( + name = "build_types", + srcs = [":npm_module_types"], + visibility = ["//visibility:public"], +) diff --git a/packages/core/lifecycle/core-lifecycle-browser/README.md b/packages/core/lifecycle/core-lifecycle-browser/README.md new file mode 100644 index 000000000000..c890d9178fe8 --- /dev/null +++ b/packages/core/lifecycle/core-lifecycle-browser/README.md @@ -0,0 +1,5 @@ +# @kbn/core-lifecycle-browser + +This package contains the public types for core's lifecycle contracts: +- `CoreSetup` +- `CoreStart` diff --git a/packages/core/lifecycle/core-lifecycle-browser/index.ts b/packages/core/lifecycle/core-lifecycle-browser/index.ts new file mode 100644 index 000000000000..22d314845830 --- /dev/null +++ b/packages/core/lifecycle/core-lifecycle-browser/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 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 { CoreSetup, CoreStart, StartServicesAccessor } from './src'; diff --git a/packages/core/lifecycle/core-lifecycle-browser/jest.config.js b/packages/core/lifecycle/core-lifecycle-browser/jest.config.js new file mode 100644 index 000000000000..0f7dc563d56e --- /dev/null +++ b/packages/core/lifecycle/core-lifecycle-browser/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/core/lifecycle/core-lifecycle-browser'], +}; diff --git a/packages/core/lifecycle/core-lifecycle-browser/kibana.jsonc b/packages/core/lifecycle/core-lifecycle-browser/kibana.jsonc new file mode 100644 index 000000000000..e17c98379b11 --- /dev/null +++ b/packages/core/lifecycle/core-lifecycle-browser/kibana.jsonc @@ -0,0 +1,7 @@ +{ + "type": "shared-common", + "id": "@kbn/core-lifecycle-browser", + "owner": "@elastic/kibana-core", + "runtimeDeps": [], + "typeDeps": [], +} diff --git a/packages/core/lifecycle/core-lifecycle-browser/package.json b/packages/core/lifecycle/core-lifecycle-browser/package.json new file mode 100644 index 000000000000..0e14a56f8bf1 --- /dev/null +++ b/packages/core/lifecycle/core-lifecycle-browser/package.json @@ -0,0 +1,9 @@ +{ + "name": "@kbn/core-lifecycle-browser", + "private": true, + "version": "1.0.0", + "main": "./target_node/index.js", + "browser": "./target_web/index.js", + "author": "Kibana Core", + "license": "SSPL-1.0 OR Elastic License 2.0" +} diff --git a/packages/core/lifecycle/core-lifecycle-browser/src/core_setup.ts b/packages/core/lifecycle/core-lifecycle-browser/src/core_setup.ts new file mode 100644 index 000000000000..0df6e7a25086 --- /dev/null +++ b/packages/core/lifecycle/core-lifecycle-browser/src/core_setup.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 { InjectedMetadataSetup } from '@kbn/core-injected-metadata-browser'; +import type { ThemeServiceSetup } from '@kbn/core-theme-browser'; +import type { AnalyticsServiceSetup } from '@kbn/core-analytics-browser'; +import type { ExecutionContextSetup } from '@kbn/core-execution-context-browser'; +import type { HttpSetup } from '@kbn/core-http-browser'; +import type { FatalErrorsSetup } from '@kbn/core-fatal-errors-browser'; +import type { IUiSettingsClient } from '@kbn/core-ui-settings-browser'; +import type { NotificationsSetup } from '@kbn/core-notifications-browser'; +import type { ApplicationSetup } from '@kbn/core-application-browser'; +import type { CoreStart } from './core_start'; + +/** + * Core services exposed to the `Plugin` setup lifecycle + * + * @typeParam TPluginsStart - the type of the consuming plugin's start dependencies. Should be the same + * as the consuming {@link Plugin}'s `TPluginsStart` type. Used by `getStartServices`. + * @typeParam TStart - the type of the consuming plugin's start contract. Should be the same as the + * consuming {@link Plugin}'s `TStart` type. Used by `getStartServices`. + * + * @public + * + * @internalRemarks We document the properties with \@link tags to improve + * navigation in the generated docs until there's a fix for + * https://github.com/Microsoft/web-build-tools/issues/1237 + */ +export interface CoreSetup { + /** {@link AnalyticsServiceSetup} */ + analytics: AnalyticsServiceSetup; + /** {@link ApplicationSetup} */ + application: ApplicationSetup; + /** {@link FatalErrorsSetup} */ + fatalErrors: FatalErrorsSetup; + /** {@link HttpSetup} */ + http: HttpSetup; + /** {@link NotificationsSetup} */ + notifications: NotificationsSetup; + /** {@link IUiSettingsClient} */ + uiSettings: IUiSettingsClient; + /** {@link ExecutionContextSetup} */ + executionContext: ExecutionContextSetup; + /** {@link InjectedMetadataSetup} */ + injectedMetadata: InjectedMetadataSetup; + /** {@link ThemeServiceSetup} */ + theme: ThemeServiceSetup; + /** {@link StartServicesAccessor} */ + getStartServices: StartServicesAccessor; +} + +/** + * Allows plugins to get access to APIs available in start inside async + * handlers, such as {@link App.mount}. Promise will not resolve until Core + * and plugin dependencies have completed `start`. + * + * @public + */ +export type StartServicesAccessor< + TPluginsStart extends object = object, + TStart = unknown +> = () => Promise<[CoreStart, TPluginsStart, TStart]>; diff --git a/packages/core/lifecycle/core-lifecycle-browser/src/core_start.ts b/packages/core/lifecycle/core-lifecycle-browser/src/core_start.ts new file mode 100644 index 000000000000..9cd702645535 --- /dev/null +++ b/packages/core/lifecycle/core-lifecycle-browser/src/core_start.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 type { InjectedMetadataStart } from '@kbn/core-injected-metadata-browser'; +import type { DocLinksStart } from '@kbn/core-doc-links-browser'; +import type { ThemeServiceStart } from '@kbn/core-theme-browser'; +import type { AnalyticsServiceStart } from '@kbn/core-analytics-browser'; +import type { ExecutionContextStart } from '@kbn/core-execution-context-browser'; +import type { HttpStart } from '@kbn/core-http-browser'; +import type { I18nStart } from '@kbn/core-i18n-browser'; +import type { FatalErrorsStart } from '@kbn/core-fatal-errors-browser'; +import type { IUiSettingsClient } from '@kbn/core-ui-settings-browser'; +import type { DeprecationsServiceStart } from '@kbn/core-deprecations-browser'; +import type { OverlayStart } from '@kbn/core-overlays-browser'; +import type { SavedObjectsStart } from '@kbn/core-saved-objects-browser'; +import type { NotificationsStart } from '@kbn/core-notifications-browser'; +import type { ApplicationStart } from '@kbn/core-application-browser'; +import type { ChromeStart } from '@kbn/core-chrome-browser'; + +/** + * Core services exposed to the `Plugin` start lifecycle + * + * @public + * + * @internalRemarks We document the properties with \@link tags to improve + * navigation in the generated docs until there's a fix for + * https://github.com/Microsoft/web-build-tools/issues/1237 + */ +export interface CoreStart { + /** {@link AnalyticsServiceStart} */ + analytics: AnalyticsServiceStart; + /** {@link ApplicationStart} */ + application: ApplicationStart; + /** {@link ChromeStart} */ + chrome: ChromeStart; + /** {@link DocLinksStart} */ + docLinks: DocLinksStart; + /** {@link ExecutionContextStart} */ + executionContext: ExecutionContextStart; + /** {@link HttpStart} */ + http: HttpStart; + /** {@link SavedObjectsStart} */ + savedObjects: SavedObjectsStart; + /** {@link I18nStart} */ + i18n: I18nStart; + /** {@link NotificationsStart} */ + notifications: NotificationsStart; + /** {@link OverlayStart} */ + overlays: OverlayStart; + /** {@link IUiSettingsClient} */ + uiSettings: IUiSettingsClient; + /** {@link FatalErrorsStart} */ + fatalErrors: FatalErrorsStart; + /** {@link DeprecationsServiceStart} */ + deprecations: DeprecationsServiceStart; + /** {@link ThemeServiceStart} */ + theme: ThemeServiceStart; + /** {@link InjectedMetadataStart} */ + injectedMetadata: InjectedMetadataStart; +} diff --git a/packages/core/lifecycle/core-lifecycle-browser/src/index.ts b/packages/core/lifecycle/core-lifecycle-browser/src/index.ts new file mode 100644 index 000000000000..5d147b47ccde --- /dev/null +++ b/packages/core/lifecycle/core-lifecycle-browser/src/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 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 { CoreSetup, StartServicesAccessor } from './core_setup'; +export type { CoreStart } from './core_start'; diff --git a/packages/core/lifecycle/core-lifecycle-browser/tsconfig.json b/packages/core/lifecycle/core-lifecycle-browser/tsconfig.json new file mode 100644 index 000000000000..ae5054c1cd72 --- /dev/null +++ b/packages/core/lifecycle/core-lifecycle-browser/tsconfig.json @@ -0,0 +1,18 @@ +{ + "extends": "../../../../tsconfig.bazel.json", + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "emitDeclarationOnly": true, + "outDir": "target_types", + "stripInternal": false, + "types": [ + "jest", + "node" + ] + }, + "include": [ + "**/*.ts", + "**/*.tsx", + ] +} diff --git a/packages/core/plugins/core-plugins-browser-internal/BUILD.bazel b/packages/core/plugins/core-plugins-browser-internal/BUILD.bazel new file mode 100644 index 000000000000..734d78cce229 --- /dev/null +++ b/packages/core/plugins/core-plugins-browser-internal/BUILD.bazel @@ -0,0 +1,125 @@ +load("@npm//@bazel/typescript:index.bzl", "ts_config") +load("@build_bazel_rules_nodejs//:index.bzl", "js_library") +load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") + +PKG_DIRNAME = "core-plugins-browser-internal" +PKG_REQUIRE_NAME = "@kbn/core-plugins-browser-internal" + +SOURCE_FILES = glob( + [ + "**/*.ts", + "**/*.tsx", + ], + exclude = [ + "**/*.config.js", + "**/*.mock.*", + "**/*.test.*", + "**/*.stories.*", + "**/__snapshots__/**", + "**/integration_tests/**", + "**/mocks/**", + "**/scripts/**", + "**/storybook/**", + "**/test_fixtures/**", + "**/test_helpers/**", + ], +) + +SRCS = SOURCE_FILES + +filegroup( + name = "srcs", + srcs = SRCS, +) + +NPM_MODULE_EXTRA_FILES = [ + "package.json", +] + +RUNTIME_DEPS = [ + "@npm//react", + "@npm//rxjs", + "@npm//lodash", +] + +TYPES_DEPS = [ + "@npm//@types/node", + "@npm//@types/jest", + "@npm//rxjs", + "@npm//lodash", + "//packages/kbn-config:npm_module_types", + "//packages/core/base/core-base-common:npm_module_types", + "//packages/core/base/core-base-browser-internal:npm_module_types", + "//packages/core/injected-metadata/core-injected-metadata-common-internal:npm_module_types", + "//packages/core/lifecycle/core-lifecycle-browser:npm_module_types", + "//packages/core/lifecycle/core-lifecycle-browser-internal:npm_module_types", + "//packages/core/plugins/core-plugins-browser:npm_module_types", +] + +jsts_transpiler( + name = "target_node", + srcs = SRCS, + build_pkg_name = package_name(), +) + +jsts_transpiler( + name = "target_web", + srcs = SRCS, + build_pkg_name = package_name(), + web = True, +) + +ts_config( + name = "tsconfig", + src = "tsconfig.json", + deps = [ + "//:tsconfig.base.json", + "//:tsconfig.bazel.json", + ], +) + +ts_project( + name = "tsc_types", + args = ['--pretty'], + srcs = SRCS, + deps = TYPES_DEPS, + declaration = True, + declaration_map = True, + emit_declaration_only = True, + out_dir = "target_types", + tsconfig = ":tsconfig", +) + +js_library( + name = PKG_DIRNAME, + srcs = NPM_MODULE_EXTRA_FILES, + deps = RUNTIME_DEPS + [":target_node", ":target_web"], + package_name = PKG_REQUIRE_NAME, + visibility = ["//visibility:public"], +) + +pkg_npm( + name = "npm_module", + deps = [":" + PKG_DIRNAME], +) + +filegroup( + name = "build", + srcs = [":npm_module"], + visibility = ["//visibility:public"], +) + +pkg_npm_types( + name = "npm_module_types", + srcs = SRCS, + deps = [":tsc_types"], + package_name = PKG_REQUIRE_NAME, + tsconfig = ":tsconfig", + visibility = ["//visibility:public"], +) + +filegroup( + name = "build_types", + srcs = [":npm_module_types"], + visibility = ["//visibility:public"], +) diff --git a/packages/core/plugins/core-plugins-browser-internal/README.md b/packages/core/plugins/core-plugins-browser-internal/README.md new file mode 100644 index 000000000000..17891ea31421 --- /dev/null +++ b/packages/core/plugins/core-plugins-browser-internal/README.md @@ -0,0 +1,3 @@ +# @kbn/core-plugins-browser-internal + +This package contains the internal types and implementation for Core's browser-side `plugins` service. diff --git a/packages/core/plugins/core-plugins-browser-internal/index.ts b/packages/core/plugins/core-plugins-browser-internal/index.ts new file mode 100644 index 000000000000..6f251aace7e0 --- /dev/null +++ b/packages/core/plugins/core-plugins-browser-internal/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 { PluginsService } from './src'; +export type { + PluginsServiceSetup, + PluginsServiceStart, + PluginsServiceSetupDeps, + PluginsServiceStartDeps, +} from './src'; diff --git a/packages/core/plugins/core-plugins-browser-internal/jest.config.js b/packages/core/plugins/core-plugins-browser-internal/jest.config.js new file mode 100644 index 000000000000..a8bf5db34a65 --- /dev/null +++ b/packages/core/plugins/core-plugins-browser-internal/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/core/plugins/core-plugins-browser-internal'], +}; diff --git a/packages/core/plugins/core-plugins-browser-internal/kibana.jsonc b/packages/core/plugins/core-plugins-browser-internal/kibana.jsonc new file mode 100644 index 000000000000..61935e6670ae --- /dev/null +++ b/packages/core/plugins/core-plugins-browser-internal/kibana.jsonc @@ -0,0 +1,7 @@ +{ + "type": "shared-common", + "id": "@kbn/core-plugins-browser-internal", + "owner": "@elastic/kibana-core", + "runtimeDeps": [], + "typeDeps": [], +} diff --git a/packages/core/plugins/core-plugins-browser-internal/package.json b/packages/core/plugins/core-plugins-browser-internal/package.json new file mode 100644 index 000000000000..0820932cb2f9 --- /dev/null +++ b/packages/core/plugins/core-plugins-browser-internal/package.json @@ -0,0 +1,9 @@ +{ + "name": "@kbn/core-plugins-browser-internal", + "private": true, + "version": "1.0.0", + "main": "./target_node/index.js", + "browser": "./target_web/index.js", + "author": "Kibana Core", + "license": "SSPL-1.0 OR Elastic License 2.0" +} diff --git a/src/core/public/plugins/index.ts b/packages/core/plugins/core-plugins-browser-internal/src/index.ts similarity index 68% rename from src/core/public/plugins/index.ts rename to packages/core/plugins/core-plugins-browser-internal/src/index.ts index 976ec660ac9b..f3f78eb4708b 100644 --- a/src/core/public/plugins/index.ts +++ b/packages/core/plugins/core-plugins-browser-internal/src/index.ts @@ -7,6 +7,9 @@ */ export { PluginsService } from './plugins_service'; -export type { Plugin, PluginInitializer } from './plugin'; -export type { PluginInitializerContext } from './plugin_context'; -export type { PluginOpaqueId } from '../../server/types'; +export type { + PluginsServiceSetup, + PluginsServiceStart, + PluginsServiceSetupDeps, + PluginsServiceStartDeps, +} from './plugins_service'; diff --git a/src/core/public/plugins/plugin.test.mocks.ts b/packages/core/plugins/core-plugins-browser-internal/src/plugin.test.mocks.ts similarity index 100% rename from src/core/public/plugins/plugin.test.mocks.ts rename to packages/core/plugins/core-plugins-browser-internal/src/plugin.test.mocks.ts diff --git a/src/core/public/plugins/plugin.test.ts b/packages/core/plugins/core-plugins-browser-internal/src/plugin.test.ts similarity index 95% rename from src/core/public/plugins/plugin.test.ts rename to packages/core/plugins/core-plugins-browser-internal/src/plugin.test.ts index 2c3bfc6961fb..f4df372b30a0 100644 --- a/src/core/public/plugins/plugin.test.ts +++ b/packages/core/plugins/core-plugins-browser-internal/src/plugin.test.ts @@ -8,8 +8,8 @@ import { mockInitializer, mockPlugin, mockPluginReader } from './plugin.test.mocks'; -import { DiscoveredPlugin, PluginType } from '../../server'; -import { coreMock } from '../mocks'; +import { DiscoveredPlugin, PluginType } from '@kbn/core-base-common'; +import { createPluginInitializerContextMock } from './test_helpers'; import { PluginWrapper } from './plugin'; function createManifest( @@ -32,7 +32,7 @@ function createManifest( let plugin: PluginWrapper>; const opaqueId = Symbol(); -const initializerContext = coreMock.createPluginInitializerContext(); +const initializerContext = createPluginInitializerContextMock(); beforeEach(() => { mockPluginReader.mockClear(); diff --git a/src/core/public/plugins/plugin.ts b/packages/core/plugins/core-plugins-browser-internal/src/plugin.ts similarity index 81% rename from src/core/public/plugins/plugin.ts rename to packages/core/plugins/core-plugins-browser-internal/src/plugin.ts index 8abab5081e55..c61d9afb175c 100644 --- a/src/core/public/plugins/plugin.ts +++ b/packages/core/plugins/core-plugins-browser-internal/src/plugin.ts @@ -7,39 +7,14 @@ */ import { firstValueFrom, Subject } from 'rxjs'; -import type { DiscoveredPlugin, PluginOpaqueId } from '../../server'; -import { PluginInitializerContext } from './plugin_context'; +import type { DiscoveredPlugin, PluginOpaqueId } from '@kbn/core-base-common'; +import type { CoreStart, CoreSetup } from '@kbn/core-lifecycle-browser'; +import type { + Plugin, + PluginInitializer, + PluginInitializerContext, +} from '@kbn/core-plugins-browser'; import { read } from './plugin_reader'; -import { CoreStart, CoreSetup } from '..'; - -/** - * The interface that should be returned by a `PluginInitializer`. - * - * @public - */ -export interface Plugin< - TSetup = void, - TStart = void, - TPluginsSetup extends object = object, - TPluginsStart extends object = object -> { - setup(core: CoreSetup, plugins: TPluginsSetup): TSetup; - start(core: CoreStart, plugins: TPluginsStart): TStart; - stop?(): void; -} - -/** - * The `plugin` export at the root of a plugin's `public` directory should conform - * to this interface. - * - * @public - */ -export type PluginInitializer< - TSetup, - TStart, - TPluginsSetup extends object = object, - TPluginsStart extends object = object -> = (core: PluginInitializerContext) => Plugin; /** * Lightweight wrapper around discovered plugin that is responsible for instantiating diff --git a/src/core/public/plugins/plugin_context.ts b/packages/core/plugins/core-plugins-browser-internal/src/plugin_context.ts similarity index 84% rename from src/core/public/plugins/plugin_context.ts rename to packages/core/plugins/core-plugins-browser-internal/src/plugin_context.ts index e3516ce5da78..41643b0e0250 100644 --- a/src/core/public/plugins/plugin_context.ts +++ b/packages/core/plugins/core-plugins-browser-internal/src/plugin_context.ts @@ -8,30 +8,11 @@ import { omit } from 'lodash'; import type { CoreContext } from '@kbn/core-base-browser-internal'; -import type { DiscoveredPlugin } from '../../server'; -import type { PluginOpaqueId, PackageInfo, EnvironmentMode } from '../../server/types'; +import type { DiscoveredPlugin, PluginOpaqueId } from '@kbn/core-base-common'; +import type { CoreSetup, CoreStart } from '@kbn/core-lifecycle-browser'; +import type { PluginInitializerContext } from '@kbn/core-plugins-browser'; import { PluginWrapper } from './plugin'; import { PluginsServiceSetupDeps, PluginsServiceStartDeps } from './plugins_service'; -import { CoreSetup, CoreStart } from '..'; - -/** - * The available core services passed to a `PluginInitializer` - * - * @public - */ -export interface PluginInitializerContext { - /** - * A symbol used to identify this plugin in the system. Needed when registering handlers or context providers. - */ - readonly opaqueId: PluginOpaqueId; - readonly env: { - mode: Readonly; - packageInfo: Readonly; - }; - readonly config: { - get: () => T; - }; -} /** * Provides a plugin-specific context passed to the plugin's constructor. This is currently diff --git a/src/core/public/plugins/plugin_reader.test.ts b/packages/core/plugins/core-plugins-browser-internal/src/plugin_reader.test.ts similarity index 100% rename from src/core/public/plugins/plugin_reader.test.ts rename to packages/core/plugins/core-plugins-browser-internal/src/plugin_reader.test.ts diff --git a/src/core/public/plugins/plugin_reader.ts b/packages/core/plugins/core-plugins-browser-internal/src/plugin_reader.ts similarity index 95% rename from src/core/public/plugins/plugin_reader.ts rename to packages/core/plugins/core-plugins-browser-internal/src/plugin_reader.ts index 24508075975d..53dd18bd6868 100644 --- a/src/core/public/plugins/plugin_reader.ts +++ b/packages/core/plugins/core-plugins-browser-internal/src/plugin_reader.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { PluginInitializer } from './plugin'; +import type { PluginInitializer } from '@kbn/core-plugins-browser'; /** * Unknown variant for internal use only for when plugins are not known. diff --git a/src/core/public/plugins/plugins_service.test.mocks.ts b/packages/core/plugins/core-plugins-browser-internal/src/plugins_service.test.mocks.ts similarity index 86% rename from src/core/public/plugins/plugins_service.test.mocks.ts rename to packages/core/plugins/core-plugins-browser-internal/src/plugins_service.test.mocks.ts index 7cf65f0d37db..34198c0b35e1 100644 --- a/src/core/public/plugins/plugins_service.test.mocks.ts +++ b/packages/core/plugins/core-plugins-browser-internal/src/plugins_service.test.mocks.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import { PluginName } from '../../server'; -import { Plugin } from './plugin'; +import type { PluginName } from '@kbn/core-base-common'; +import type { Plugin } from '@kbn/core-plugins-browser'; export type MockedPluginInitializer = jest.Mock>; diff --git a/src/core/public/plugins/plugins_service.test.ts b/packages/core/plugins/core-plugins-browser-internal/src/plugins_service.test.ts similarity index 97% rename from src/core/public/plugins/plugins_service.test.ts rename to packages/core/plugins/core-plugins-browser-internal/src/plugins_service.test.ts index 78f27f0c31a5..f2023abec195 100644 --- a/src/core/public/plugins/plugins_service.test.ts +++ b/packages/core/plugins/core-plugins-browser-internal/src/plugins_service.test.ts @@ -13,14 +13,14 @@ import { mockPluginInitializerProvider, } from './plugins_service.test.mocks'; -import { type PluginName, PluginType } from '../../server'; +import { type PluginName, PluginType } from '@kbn/core-base-common'; import { analyticsServiceMock } from '@kbn/core-analytics-browser-mocks'; import { docLinksServiceMock } from '@kbn/core-doc-links-browser-mocks'; import { executionContextServiceMock } from '@kbn/core-execution-context-browser-mocks'; import { i18nServiceMock } from '@kbn/core-i18n-browser-mocks'; import { injectedMetadataServiceMock } from '@kbn/core-injected-metadata-browser-mocks'; import { themeServiceMock } from '@kbn/core-theme-browser-mocks'; -import { coreMock } from '../mocks'; +import { coreContextMock } from '@kbn/core-base-browser-mocks'; import { PluginsService, @@ -36,7 +36,8 @@ import { chromeServiceMock } from '@kbn/core-chrome-browser-mocks'; import { fatalErrorsServiceMock } from '@kbn/core-fatal-errors-browser-mocks'; import { uiSettingsServiceMock } from '@kbn/core-ui-settings-browser-mocks'; import { httpServiceMock } from '@kbn/core-http-browser-mocks'; -import type { CoreSetup, CoreStart, PluginInitializerContext } from '..'; +import type { PluginInitializerContext } from '@kbn/core-plugins-browser'; +import type { CoreSetup, CoreStart } from '@kbn/core-lifecycle-browser'; import { savedObjectsServiceMock } from '@kbn/core-saved-objects-browser-mocks'; import { deprecationsServiceMock } from '@kbn/core-deprecations-browser-mocks'; @@ -50,7 +51,7 @@ let plugins: InjectedMetadataPlugin[]; type DeeplyMocked = { [P in keyof T]: jest.Mocked }; -const mockCoreContext = coreMock.createCoreContext(); +const mockCoreContext = coreContextMock.create(); let mockSetupDeps: DeeplyMocked; let mockSetupContext: DeeplyMocked; let mockStartDeps: DeeplyMocked; diff --git a/src/core/public/plugins/plugins_service.ts b/packages/core/plugins/core-plugins-browser-internal/src/plugins_service.ts similarity index 98% rename from src/core/public/plugins/plugins_service.ts rename to packages/core/plugins/core-plugins-browser-internal/src/plugins_service.ts index dc0a77d096ff..4e10796fd3a6 100644 --- a/src/core/public/plugins/plugins_service.ts +++ b/packages/core/plugins/core-plugins-browser-internal/src/plugins_service.ts @@ -9,13 +9,13 @@ import type { CoreService, CoreContext } from '@kbn/core-base-browser-internal'; import type { PluginName, PluginOpaqueId } from '@kbn/core-base-common'; import type { InjectedMetadataPlugin } from '@kbn/core-injected-metadata-common-internal'; +import type { InternalCoreSetup, InternalCoreStart } from '@kbn/core-lifecycle-browser-internal'; import { PluginWrapper } from './plugin'; import { createPluginInitializerContext, createPluginSetupContext, createPluginStartContext, } from './plugin_context'; -import { InternalCoreSetup, InternalCoreStart } from '../core_system'; /** @internal */ export type PluginsServiceSetupDeps = InternalCoreSetup; diff --git a/packages/core/plugins/core-plugins-browser-internal/src/test_helpers/index.ts b/packages/core/plugins/core-plugins-browser-internal/src/test_helpers/index.ts new file mode 100644 index 000000000000..ee660a8c1f8b --- /dev/null +++ b/packages/core/plugins/core-plugins-browser-internal/src/test_helpers/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 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 { createPluginInitializerContextMock } from './mocks'; diff --git a/packages/core/plugins/core-plugins-browser-internal/src/test_helpers/mocks.ts b/packages/core/plugins/core-plugins-browser-internal/src/test_helpers/mocks.ts new file mode 100644 index 000000000000..fcd4e80c02de --- /dev/null +++ b/packages/core/plugins/core-plugins-browser-internal/src/test_helpers/mocks.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 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 { PluginInitializerContext } from '@kbn/core-plugins-browser'; + +export const createPluginInitializerContextMock = (config: unknown = {}) => { + const mock: PluginInitializerContext = { + opaqueId: Symbol(), + env: { + mode: { + dev: true, + name: 'development', + prod: false, + }, + packageInfo: { + version: 'version', + branch: 'branch', + buildNum: 100, + buildSha: 'buildSha', + dist: false, + }, + }, + config: { + get: () => config as T, + }, + }; + + return mock; +}; diff --git a/packages/core/plugins/core-plugins-browser-internal/tsconfig.json b/packages/core/plugins/core-plugins-browser-internal/tsconfig.json new file mode 100644 index 000000000000..4283cbe1b760 --- /dev/null +++ b/packages/core/plugins/core-plugins-browser-internal/tsconfig.json @@ -0,0 +1,18 @@ +{ + "extends": "../../../../tsconfig.bazel.json", + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "emitDeclarationOnly": true, + "outDir": "target_types", + "stripInternal": false, + "types": [ + "jest", + "node", + ] + }, + "include": [ + "**/*.ts", + "**/*.tsx", + ] +} diff --git a/packages/core/plugins/core-plugins-browser-mocks/BUILD.bazel b/packages/core/plugins/core-plugins-browser-mocks/BUILD.bazel new file mode 100644 index 000000000000..0d334ef02a29 --- /dev/null +++ b/packages/core/plugins/core-plugins-browser-mocks/BUILD.bazel @@ -0,0 +1,114 @@ +load("@npm//@bazel/typescript:index.bzl", "ts_config") +load("@build_bazel_rules_nodejs//:index.bzl", "js_library") +load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") + +PKG_DIRNAME = "core-plugins-browser-mocks" +PKG_REQUIRE_NAME = "@kbn/core-plugins-browser-mocks" + +SOURCE_FILES = glob( + [ + "**/*.ts", + "**/*.tsx", + ], + exclude = [ + "**/*.config.js", + "**/*.test.*", + "**/*.stories.*", + "**/__snapshots__/**", + "**/integration_tests/**", + "**/mocks/**", + "**/scripts/**", + "**/storybook/**", + "**/test_fixtures/**", + "**/test_helpers/**", + ], +) + +SRCS = SOURCE_FILES + +filegroup( + name = "srcs", + srcs = SRCS, +) + +NPM_MODULE_EXTRA_FILES = [ + "package.json", +] + +RUNTIME_DEPS = [ +] + +TYPES_DEPS = [ + "@npm//@types/node", + "@npm//@types/jest", + "//packages/kbn-utility-types:npm_module_types", + "//packages/core/plugins/core-plugins-browser-internal:npm_module_types", +] + +jsts_transpiler( + name = "target_node", + srcs = SRCS, + build_pkg_name = package_name(), +) + +jsts_transpiler( + name = "target_web", + srcs = SRCS, + build_pkg_name = package_name(), + web = True, +) + +ts_config( + name = "tsconfig", + src = "tsconfig.json", + deps = [ + "//:tsconfig.base.json", + "//:tsconfig.bazel.json", + ], +) + +ts_project( + name = "tsc_types", + args = ['--pretty'], + srcs = SRCS, + deps = TYPES_DEPS, + declaration = True, + declaration_map = True, + emit_declaration_only = True, + out_dir = "target_types", + tsconfig = ":tsconfig", +) + +js_library( + name = PKG_DIRNAME, + srcs = NPM_MODULE_EXTRA_FILES, + deps = RUNTIME_DEPS + [":target_node", ":target_web"], + package_name = PKG_REQUIRE_NAME, + visibility = ["//visibility:public"], +) + +pkg_npm( + name = "npm_module", + deps = [":" + PKG_DIRNAME], +) + +filegroup( + name = "build", + srcs = [":npm_module"], + visibility = ["//visibility:public"], +) + +pkg_npm_types( + name = "npm_module_types", + srcs = SRCS, + deps = [":tsc_types"], + package_name = PKG_REQUIRE_NAME, + tsconfig = ":tsconfig", + visibility = ["//visibility:public"], +) + +filegroup( + name = "build_types", + srcs = [":npm_module_types"], + visibility = ["//visibility:public"], +) diff --git a/packages/core/plugins/core-plugins-browser-mocks/README.md b/packages/core/plugins/core-plugins-browser-mocks/README.md new file mode 100644 index 000000000000..f379ef7d953e --- /dev/null +++ b/packages/core/plugins/core-plugins-browser-mocks/README.md @@ -0,0 +1,4 @@ +# @kbn/core-plugins-browser-mocks + +This package contains mocks for Core's browser-side `plugins` service. +- `pluginsServiceMock` diff --git a/packages/core/plugins/core-plugins-browser-mocks/index.ts b/packages/core/plugins/core-plugins-browser-mocks/index.ts new file mode 100644 index 000000000000..3c9ecebdb3d1 --- /dev/null +++ b/packages/core/plugins/core-plugins-browser-mocks/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 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 { pluginsServiceMock } from './src'; diff --git a/packages/core/plugins/core-plugins-browser-mocks/jest.config.js b/packages/core/plugins/core-plugins-browser-mocks/jest.config.js new file mode 100644 index 000000000000..f1072f43bde2 --- /dev/null +++ b/packages/core/plugins/core-plugins-browser-mocks/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/core/plugins/core-plugins-browser-mocks'], +}; diff --git a/packages/core/plugins/core-plugins-browser-mocks/kibana.jsonc b/packages/core/plugins/core-plugins-browser-mocks/kibana.jsonc new file mode 100644 index 000000000000..c451ce7aac05 --- /dev/null +++ b/packages/core/plugins/core-plugins-browser-mocks/kibana.jsonc @@ -0,0 +1,7 @@ +{ + "type": "shared-common", + "id": "@kbn/core-plugins-browser-mocks", + "owner": "@elastic/kibana-core", + "runtimeDeps": [], + "typeDeps": [], +} diff --git a/packages/core/plugins/core-plugins-browser-mocks/package.json b/packages/core/plugins/core-plugins-browser-mocks/package.json new file mode 100644 index 000000000000..98090f042ab0 --- /dev/null +++ b/packages/core/plugins/core-plugins-browser-mocks/package.json @@ -0,0 +1,9 @@ +{ + "name": "@kbn/core-plugins-browser-mocks", + "private": true, + "version": "1.0.0", + "main": "./target_node/index.js", + "browser": "./target_web/index.js", + "author": "Kibana Core", + "license": "SSPL-1.0 OR Elastic License 2.0" +} diff --git a/packages/core/plugins/core-plugins-browser-mocks/src/index.ts b/packages/core/plugins/core-plugins-browser-mocks/src/index.ts new file mode 100644 index 000000000000..9b04e0fbbeb4 --- /dev/null +++ b/packages/core/plugins/core-plugins-browser-mocks/src/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 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 { pluginsServiceMock } from './plugins_service.mock'; diff --git a/src/core/public/plugins/plugins_service.mock.ts b/packages/core/plugins/core-plugins-browser-mocks/src/plugins_service.mock.ts similarity index 66% rename from src/core/public/plugins/plugins_service.mock.ts rename to packages/core/plugins/core-plugins-browser-mocks/src/plugins_service.mock.ts index cfc174d106a8..c1539e787368 100644 --- a/src/core/public/plugins/plugins_service.mock.ts +++ b/packages/core/plugins/core-plugins-browser-mocks/src/plugins_service.mock.ts @@ -7,7 +7,8 @@ */ import type { PublicMethodsOf } from '@kbn/utility-types'; -import { PluginsService, PluginsServiceSetup } from './plugins_service'; +import type { PluginInitializerContext } from '@kbn/core-plugins-browser'; +import type { PluginsService, PluginsServiceSetup } from '@kbn/core-plugins-browser-internal'; const createSetupContractMock = () => { const setupContract: jest.Mocked = { @@ -25,6 +26,31 @@ const createStartContractMock = () => { return startContract as PluginsServiceSetup; }; +const createPluginInitializerContextMock = (config: unknown = {}) => { + const mock: PluginInitializerContext = { + opaqueId: Symbol(), + env: { + mode: { + dev: true, + name: 'development', + prod: false, + }, + packageInfo: { + version: 'version', + branch: 'branch', + buildNum: 100, + buildSha: 'buildSha', + dist: false, + }, + }, + config: { + get: () => config as T, + }, + }; + + return mock; +}; + type PluginsServiceContract = PublicMethodsOf; const createMock = () => { const mocked: jest.Mocked = { @@ -43,4 +69,5 @@ export const pluginsServiceMock = { create: createMock, createSetupContract: createSetupContractMock, createStartContract: createStartContractMock, + createPluginInitializerContext: createPluginInitializerContextMock, }; diff --git a/packages/core/plugins/core-plugins-browser-mocks/tsconfig.json b/packages/core/plugins/core-plugins-browser-mocks/tsconfig.json new file mode 100644 index 000000000000..4283cbe1b760 --- /dev/null +++ b/packages/core/plugins/core-plugins-browser-mocks/tsconfig.json @@ -0,0 +1,18 @@ +{ + "extends": "../../../../tsconfig.bazel.json", + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "emitDeclarationOnly": true, + "outDir": "target_types", + "stripInternal": false, + "types": [ + "jest", + "node", + ] + }, + "include": [ + "**/*.ts", + "**/*.tsx", + ] +} diff --git a/packages/core/plugins/core-plugins-browser/BUILD.bazel b/packages/core/plugins/core-plugins-browser/BUILD.bazel new file mode 100644 index 000000000000..fca01ef98d10 --- /dev/null +++ b/packages/core/plugins/core-plugins-browser/BUILD.bazel @@ -0,0 +1,116 @@ +load("@npm//@bazel/typescript:index.bzl", "ts_config") +load("@build_bazel_rules_nodejs//:index.bzl", "js_library") +load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") + +PKG_DIRNAME = "core-plugins-browser" +PKG_REQUIRE_NAME = "@kbn/core-plugins-browser" + +SOURCE_FILES = glob( + [ + "**/*.ts", + "**/*.tsx", + ], + exclude = [ + "**/*.config.js", + "**/*.mock.*", + "**/*.test.*", + "**/*.stories.*", + "**/__snapshots__/**", + "**/integration_tests/**", + "**/mocks/**", + "**/scripts/**", + "**/storybook/**", + "**/test_fixtures/**", + "**/test_helpers/**", + ], +) + +SRCS = SOURCE_FILES + +filegroup( + name = "srcs", + srcs = SRCS, +) + +NPM_MODULE_EXTRA_FILES = [ + "package.json", +] + +RUNTIME_DEPS = [ +] + +TYPES_DEPS = [ + "@npm//@types/node", + "@npm//@types/jest", + "//packages/kbn-config:npm_module_types", + "//packages/core/base/core-base-common:npm_module_types", + "//packages/core/lifecycle/core-lifecycle-browser:npm_module_types", +] + +jsts_transpiler( + name = "target_node", + srcs = SRCS, + build_pkg_name = package_name(), +) + +jsts_transpiler( + name = "target_web", + srcs = SRCS, + build_pkg_name = package_name(), + web = True, +) + +ts_config( + name = "tsconfig", + src = "tsconfig.json", + deps = [ + "//:tsconfig.base.json", + "//:tsconfig.bazel.json", + ], +) + +ts_project( + name = "tsc_types", + args = ['--pretty'], + srcs = SRCS, + deps = TYPES_DEPS, + declaration = True, + declaration_map = True, + emit_declaration_only = True, + out_dir = "target_types", + tsconfig = ":tsconfig", +) + +js_library( + name = PKG_DIRNAME, + srcs = NPM_MODULE_EXTRA_FILES, + deps = RUNTIME_DEPS + [":target_node", ":target_web"], + package_name = PKG_REQUIRE_NAME, + visibility = ["//visibility:public"], +) + +pkg_npm( + name = "npm_module", + deps = [":" + PKG_DIRNAME], +) + +filegroup( + name = "build", + srcs = [":npm_module"], + visibility = ["//visibility:public"], +) + +pkg_npm_types( + name = "npm_module_types", + srcs = SRCS, + deps = [":tsc_types"], + package_name = PKG_REQUIRE_NAME, + tsconfig = ":tsconfig", + visibility = ["//visibility:public"], +) + +filegroup( + name = "build_types", + srcs = [":npm_module_types"], + visibility = ["//visibility:public"], +) diff --git a/packages/core/plugins/core-plugins-browser/README.md b/packages/core/plugins/core-plugins-browser/README.md new file mode 100644 index 000000000000..a68e2ff32808 --- /dev/null +++ b/packages/core/plugins/core-plugins-browser/README.md @@ -0,0 +1,8 @@ +# @kbn/core-plugins-browser + +This package contains the public types for Core's browser-side `plugins` domain: +- `Plugin` +- `PluginInitializer` +- `PluginInitializerContext` + +Note: the `CoreSetup` and `CoreStart` types are available from the `@kbn/core-lifecycle-browser` package instead. diff --git a/packages/core/plugins/core-plugins-browser/index.ts b/packages/core/plugins/core-plugins-browser/index.ts new file mode 100644 index 000000000000..779ebe33097a --- /dev/null +++ b/packages/core/plugins/core-plugins-browser/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 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 { Plugin, PluginInitializer, PluginInitializerContext } from './src'; diff --git a/packages/core/plugins/core-plugins-browser/jest.config.js b/packages/core/plugins/core-plugins-browser/jest.config.js new file mode 100644 index 000000000000..b53bd873d88c --- /dev/null +++ b/packages/core/plugins/core-plugins-browser/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/core/plugins/core-plugins-browser'], +}; diff --git a/packages/core/plugins/core-plugins-browser/kibana.jsonc b/packages/core/plugins/core-plugins-browser/kibana.jsonc new file mode 100644 index 000000000000..f7457049acc0 --- /dev/null +++ b/packages/core/plugins/core-plugins-browser/kibana.jsonc @@ -0,0 +1,7 @@ +{ + "type": "shared-common", + "id": "@kbn/core-plugins-browser", + "owner": "@elastic/kibana-core", + "runtimeDeps": [], + "typeDeps": [], +} diff --git a/packages/core/plugins/core-plugins-browser/package.json b/packages/core/plugins/core-plugins-browser/package.json new file mode 100644 index 000000000000..f03af035d223 --- /dev/null +++ b/packages/core/plugins/core-plugins-browser/package.json @@ -0,0 +1,9 @@ +{ + "name": "@kbn/core-plugins-browser", + "private": true, + "version": "1.0.0", + "main": "./target_node/index.js", + "browser": "./target_web/index.js", + "author": "Kibana Core", + "license": "SSPL-1.0 OR Elastic License 2.0" +} diff --git a/packages/core/plugins/core-plugins-browser/src/index.ts b/packages/core/plugins/core-plugins-browser/src/index.ts new file mode 100644 index 000000000000..1e1f513981a0 --- /dev/null +++ b/packages/core/plugins/core-plugins-browser/src/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 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 { Plugin } from './plugin'; +export type { PluginInitializer, PluginInitializerContext } from './plugin_initializer'; diff --git a/packages/core/plugins/core-plugins-browser/src/plugin.ts b/packages/core/plugins/core-plugins-browser/src/plugin.ts new file mode 100644 index 000000000000..676e4fec39f4 --- /dev/null +++ b/packages/core/plugins/core-plugins-browser/src/plugin.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 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 { CoreStart, CoreSetup } from '@kbn/core-lifecycle-browser'; + +/** + * The interface that should be returned by a `PluginInitializer`. + * + * @public + */ +export interface Plugin< + TSetup = void, + TStart = void, + TPluginsSetup extends object = object, + TPluginsStart extends object = object +> { + setup(core: CoreSetup, plugins: TPluginsSetup): TSetup; + + start(core: CoreStart, plugins: TPluginsStart): TStart; + + stop?(): void; +} diff --git a/packages/core/plugins/core-plugins-browser/src/plugin_initializer.ts b/packages/core/plugins/core-plugins-browser/src/plugin_initializer.ts new file mode 100644 index 000000000000..14aaaff31e94 --- /dev/null +++ b/packages/core/plugins/core-plugins-browser/src/plugin_initializer.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 type { PluginOpaqueId } from '@kbn/core-base-common'; +import type { PackageInfo, EnvironmentMode } from '@kbn/config'; +import type { Plugin } from './plugin'; + +/** + * The `plugin` export at the root of a plugin's `public` directory should conform + * to this interface. + * + * @public + */ +export type PluginInitializer< + TSetup, + TStart, + TPluginsSetup extends object = object, + TPluginsStart extends object = object +> = (core: PluginInitializerContext) => Plugin; + +/** + * The available core services passed to a `PluginInitializer` + * + * @public + */ +export interface PluginInitializerContext { + /** + * A symbol used to identify this plugin in the system. Needed when registering handlers or context providers. + */ + readonly opaqueId: PluginOpaqueId; + readonly env: { + mode: Readonly; + packageInfo: Readonly; + }; + readonly config: { + get: () => T; + }; +} diff --git a/packages/core/plugins/core-plugins-browser/tsconfig.json b/packages/core/plugins/core-plugins-browser/tsconfig.json new file mode 100644 index 000000000000..4283cbe1b760 --- /dev/null +++ b/packages/core/plugins/core-plugins-browser/tsconfig.json @@ -0,0 +1,18 @@ +{ + "extends": "../../../../tsconfig.bazel.json", + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "emitDeclarationOnly": true, + "outDir": "target_types", + "stripInternal": false, + "types": [ + "jest", + "node", + ] + }, + "include": [ + "**/*.ts", + "**/*.tsx", + ] +} diff --git a/packages/kbn-test/src/functional_tests/start_servers/flags.test.ts b/packages/kbn-test/src/functional_tests/start_servers/flags.test.ts index 5f40b2ae6682..a8498d9e4e49 100644 --- a/packages/kbn-test/src/functional_tests/start_servers/flags.test.ts +++ b/packages/kbn-test/src/functional_tests/start_servers/flags.test.ts @@ -6,13 +6,19 @@ * Side Public License, v 1. */ +import Path from 'path'; + import { getFlags, FlagsReader } from '@kbn/dev-cli-runner'; import { createAnyInstanceSerializer, createAbsolutePathSerializer } from '@kbn/jest-serializers'; +import { REPO_ROOT } from '@kbn/utils'; + import { EsVersion } from '../../functional_test_runner'; import { parseFlags, FLAG_OPTIONS } from './flags'; jest.mock('uuid', () => ({ v4: () => 'some-uuid' })); +const cwdMock = (process.cwd = jest.fn().mockReturnValue(REPO_ROOT)); + expect.addSnapshotSerializer( createAnyInstanceSerializer(EsVersion, (v: EsVersion) => `EsVersion ${v.toString()}`) ); @@ -23,10 +29,27 @@ const defaults = getFlags(['--config=foo'], FLAG_OPTIONS); const test = (opts: Record) => parseFlags(new FlagsReader({ ...defaults, ...opts })); +beforeEach(() => { + cwdMock.mockReturnValue(REPO_ROOT); +}); + it('parses a subset of the flags from runTests', () => { expect(test({ config: 'foo' })).toMatchInlineSnapshot(` Object { - "config": "foo", + "config": /foo, + "esFrom": undefined, + "esVersion": , + "installDir": undefined, + "logsDir": undefined, + } + `); +}); + +it('respects the cwd of the script', () => { + cwdMock.mockReturnValue(Path.resolve(REPO_ROOT, 'x-pack')); + expect(test({ config: 'foo' })).toMatchInlineSnapshot(` + Object { + "config": /x-pack/foo, "esFrom": undefined, "esVersion": , "installDir": undefined, diff --git a/packages/kbn-test/src/functional_tests/start_servers/flags.ts b/packages/kbn-test/src/functional_tests/start_servers/flags.ts index 8ce3af9f5917..99c7de9e53ea 100644 --- a/packages/kbn-test/src/functional_tests/start_servers/flags.ts +++ b/packages/kbn-test/src/functional_tests/start_servers/flags.ts @@ -31,8 +31,8 @@ export const FLAG_OPTIONS: FlagOptions = { export function parseFlags(flags: FlagsReader) { const configs = [ - ...(flags.arrayOfStrings('config') ?? []), - ...(flags.arrayOfStrings('journey') ?? []), + ...(flags.arrayOfPaths('config') ?? []), + ...(flags.arrayOfPaths('journey') ?? []), ]; if (configs.length !== 1) { throw createFlagError(`expected exactly one --config or --journey flag`); diff --git a/packages/shared-ux/page/kibana_template/impl/src/page_template_inner.tsx b/packages/shared-ux/page/kibana_template/impl/src/page_template_inner.tsx index 762435157f97..607b8fbd1776 100644 --- a/packages/shared-ux/page/kibana_template/impl/src/page_template_inner.tsx +++ b/packages/shared-ux/page/kibana_template/impl/src/page_template_inner.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import React, { FC } from 'react'; +import React, { FC, useEffect, useState } from 'react'; import classNames from 'classnames'; import { EuiPageTemplate } from '@elastic/eui'; @@ -21,6 +21,9 @@ const getClasses = (template?: string, className?: string) => { ); }; +const KIBANA_CHROME_SELECTOR = '[data-test-subj="kibanaChrome"]'; +const HEADER_GLOBAL_NAV_SELECTOR = '[data-test-subj="headerGlobalNav"]'; + /** * A thin wrapper around EuiPageTemplate with a few Kibana specific additions */ @@ -35,6 +38,18 @@ export const KibanaPageTemplateInner: FC = ({ }) => { let header; + const [offset, setOffset] = useState(); + + useEffect(() => { + const kibanaChrome = document.querySelector(KIBANA_CHROME_SELECTOR) as HTMLElement; + if (kibanaChrome) { + const kibanaChromeHeader = kibanaChrome.querySelector( + HEADER_GLOBAL_NAV_SELECTOR + ) as HTMLElement; + setOffset(kibanaChromeHeader?.offsetTop + kibanaChromeHeader?.offsetHeight); + } + }, []); + if (isEmptyState && pageHeader && !children) { const { iconType, pageTitle, description, rightSideItems } = pageHeader; const title = pageTitle ?

{pageTitle}

: undefined; @@ -54,15 +69,11 @@ export const KibanaPageTemplateInner: FC = ({ let sideBar; if (pageSideBar) { - sideBar = ( - - {pageSideBar} - - ); + const sideBarProps = { ...pageSideBarProps }; + if (offset) { + sideBarProps.sticky = { offset }; + } + sideBar = {pageSideBar}; } const classes = getClasses(undefined, className); diff --git a/packages/shared-ux/page/solution_nav/BUILD.bazel b/packages/shared-ux/page/solution_nav/BUILD.bazel index d12304fcdd19..0b6b0a825802 100644 --- a/packages/shared-ux/page/solution_nav/BUILD.bazel +++ b/packages/shared-ux/page/solution_nav/BUILD.bazel @@ -78,6 +78,7 @@ TYPES_DEPS = [ "//packages/kbn-i18n-react:npm_module_types", "//packages/kbn-i18n:npm_module_types", "//packages/shared-ux/avatar/solution:npm_module_types", + "//packages/shared-ux/page/kibana_template/types" ] jsts_transpiler( diff --git a/packages/shared-ux/page/solution_nav/src/__snapshots__/with_solution_nav.test.tsx.snap b/packages/shared-ux/page/solution_nav/src/__snapshots__/with_solution_nav.test.tsx.snap index bae96ec7b65d..749d0a13ad8d 100644 --- a/packages/shared-ux/page/solution_nav/src/__snapshots__/with_solution_nav.test.tsx.snap +++ b/packages/shared-ux/page/solution_nav/src/__snapshots__/with_solution_nav.test.tsx.snap @@ -52,7 +52,20 @@ exports[`WithSolutionNav renders wrapped component 1`] = ` } pageSideBarProps={ Object { - "className": "kbnSolutionNav__sidebar kbnStickyMenu", + "className": "kbnStickyMenu", + "css": Object { + "map": undefined, + "name": "sx7fqw", + "next": undefined, + "styles": " + flex: 0 1 0%; + overflow: hidden; + @media screen and (prefers-reduced-motion: no-preference) { + transition: min-width 150ms cubic-bezier(.694, .0482, .335, 1); + } + ", + "toString": [Function], + }, "minWidth": undefined, "paddingSize": "none", } @@ -112,7 +125,20 @@ exports[`WithSolutionNav with children 1`] = ` } pageSideBarProps={ Object { - "className": "kbnSolutionNav__sidebar kbnStickyMenu", + "className": "kbnStickyMenu", + "css": Object { + "map": undefined, + "name": "sx7fqw", + "next": undefined, + "styles": " + flex: 0 1 0%; + overflow: hidden; + @media screen and (prefers-reduced-motion: no-preference) { + transition: min-width 150ms cubic-bezier(.694, .0482, .335, 1); + } + ", + "toString": [Function], + }, "minWidth": undefined, "paddingSize": "none", } diff --git a/packages/shared-ux/page/solution_nav/src/with_solution_nav.scss b/packages/shared-ux/page/solution_nav/src/with_solution_nav.scss deleted file mode 100644 index 00cfb7b9f927..000000000000 --- a/packages/shared-ux/page/solution_nav/src/with_solution_nav.scss +++ /dev/null @@ -1,8 +0,0 @@ -// TODO: Can now be converted to Emotion -.kbnSolutionNav__sidebar { - overflow: hidden; - - @include euiCanAnimate { - transition: min-width $euiAnimSpeedFast $euiAnimSlightResistance; - } -} diff --git a/packages/shared-ux/page/solution_nav/src/with_solution_nav.styles.ts b/packages/shared-ux/page/solution_nav/src/with_solution_nav.styles.ts new file mode 100644 index 000000000000..906f1fdd8e29 --- /dev/null +++ b/packages/shared-ux/page/solution_nav/src/with_solution_nav.styles.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 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 { css } from '@emotion/react'; +import { euiCanAnimate, EuiThemeComputed } from '@elastic/eui'; + +export const WithSolutionNavStyles = (euiTheme: EuiThemeComputed<{}>) => { + return css` + flex: 0 1 0%; + overflow: hidden; + ${euiCanAnimate} { + transition: min-width ${euiTheme.animation.fast} ${euiTheme.animation.resistance}; + } + `; +}; diff --git a/packages/shared-ux/page/solution_nav/src/with_solution_nav.tsx b/packages/shared-ux/page/solution_nav/src/with_solution_nav.tsx index a618f6d6ba41..0c3f0359c1f6 100644 --- a/packages/shared-ux/page/solution_nav/src/with_solution_nav.tsx +++ b/packages/shared-ux/page/solution_nav/src/with_solution_nav.tsx @@ -8,27 +8,20 @@ import React, { ComponentType, ReactNode, useState } from 'react'; import classNames from 'classnames'; -import { - useIsWithinBreakpoints, - useEuiTheme, - useIsWithinMinBreakpoint, - EuiPageSidebarProps, -} from '@elastic/eui'; +import { SerializedStyles } from '@emotion/serialize'; +import { KibanaPageTemplateProps } from '@kbn/shared-ux-page-kibana-template-types'; +import { useIsWithinBreakpoints, useEuiTheme, useIsWithinMinBreakpoint } from '@elastic/eui'; import { SolutionNav, SolutionNavProps } from './solution_nav'; - -import './with_solution_nav.scss'; +import { WithSolutionNavStyles } from './with_solution_nav.styles'; // https://reactjs.org/docs/higher-order-components.html#convention-wrap-the-display-name-for-easy-debugging function getDisplayName(Component: ComponentType) { return Component.displayName || Component.name || 'UnnamedComponent'; } -// TODO: Would be nice to grab these from KibanaPageTemplate or vice-versa -interface TemplateProps { - pageSideBar?: ReactNode; - pageSideBarProps?: Partial; +type TemplateProps = Pick & { children?: ReactNode; -} +}; type Props

= P & TemplateProps & { @@ -58,8 +51,8 @@ export const withSolutionNav =

(WrappedComponent: Compo const { canBeCollapsed = true } = solutionNav; const isSidebarShrunk = isMediumBreakpoint || (canBeCollapsed && isLargerBreakpoint && !isSideNavOpenOnDesktop); + const withSolutionNavStyles = WithSolutionNavStyles(euiTheme); const sideBarClasses = classNames( - 'kbnSolutionNav__sidebar', 'kbnStickyMenu', { 'kbnSolutionNav__sidebar--shrink': isSidebarShrunk, @@ -75,11 +68,12 @@ export const withSolutionNav =

(WrappedComponent: Compo /> ); - const pageSideBarProps: TemplateProps['pageSideBarProps'] = { + const pageSideBarProps: TemplateProps['pageSideBarProps'] & { css: SerializedStyles } = { paddingSize: 'none' as 'none', ...props.pageSideBarProps, minWidth: isSidebarShrunk ? euiTheme.size.xxl : undefined, className: sideBarClasses, + css: withSolutionNavStyles, }; return ( diff --git a/src/core/public/core_system.test.mocks.ts b/src/core/public/core_system.test.mocks.ts index f5b0c017dcd1..69560623e163 100644 --- a/src/core/public/core_system.test.mocks.ts +++ b/src/core/public/core_system.test.mocks.ts @@ -17,7 +17,7 @@ import { httpServiceMock } from '@kbn/core-http-browser-mocks'; import { i18nServiceMock } from '@kbn/core-i18n-browser-mocks'; import { notificationServiceMock } from '@kbn/core-notifications-browser-mocks'; import { overlayServiceMock } from '@kbn/core-overlays-browser-mocks'; -import { pluginsServiceMock } from './plugins/plugins_service.mock'; +import { pluginsServiceMock } from '@kbn/core-plugins-browser-mocks'; import { uiSettingsServiceMock } from '@kbn/core-ui-settings-browser-mocks'; import { renderingServiceMock } from '@kbn/core-rendering-browser-mocks'; import { integrationsServiceMock } from '@kbn/core-integrations-browser-mocks'; @@ -94,7 +94,7 @@ jest.doMock('@kbn/core-overlays-browser-internal', () => ({ export const MockPluginsService = pluginsServiceMock.create(); export const PluginsServiceConstructor = jest.fn().mockImplementation(() => MockPluginsService); -jest.doMock('./plugins', () => ({ +jest.doMock('@kbn/core-plugins-browser-internal', () => ({ PluginsService: PluginsServiceConstructor, })); diff --git a/src/core/public/core_system.ts b/src/core/public/core_system.ts index 7b1e073e4b53..4381cfdd2abf 100644 --- a/src/core/public/core_system.ts +++ b/src/core/public/core_system.ts @@ -11,8 +11,6 @@ import type { CoreContext } from '@kbn/core-base-browser-internal'; import { InjectedMetadataService, type InjectedMetadataParams, - type InternalInjectedMetadataSetup, - type InternalInjectedMetadataStart, } from '@kbn/core-injected-metadata-browser-internal'; import { DocLinksService } from '@kbn/core-doc-links-browser-internal'; import { ThemeService } from '@kbn/core-theme-browser-internal'; @@ -32,16 +30,12 @@ import { KBN_LOAD_MARKS } from '@kbn/core-mount-utils-browser-internal'; import { SavedObjectsService } from '@kbn/core-saved-objects-browser-internal'; import { NotificationsService } from '@kbn/core-notifications-browser-internal'; import { ChromeService } from '@kbn/core-chrome-browser-internal'; -import { - ApplicationService, - type InternalApplicationSetup, - type InternalApplicationStart, -} from '@kbn/core-application-browser-internal'; +import { ApplicationService } from '@kbn/core-application-browser-internal'; import { RenderingService } from '@kbn/core-rendering-browser-internal'; import { CoreAppsService } from '@kbn/core-apps-browser-internal'; +import type { InternalCoreSetup, InternalCoreStart } from '@kbn/core-lifecycle-browser-internal'; +import { PluginsService } from '@kbn/core-plugins-browser-internal'; import { fetchOptionalMemoryInfo } from './fetch_optional_memory_info'; -import { CoreSetup, CoreStart } from '.'; -import { PluginsService } from './plugins'; import { LOAD_SETUP_DONE, @@ -59,18 +53,6 @@ interface Params { injectedMetadata: InjectedMetadataParams['injectedMetadata']; } -/** @internal */ -export interface InternalCoreSetup extends Omit { - application: InternalApplicationSetup; - injectedMetadata: InternalInjectedMetadataSetup; -} - -/** @internal */ -export interface InternalCoreStart extends Omit { - application: InternalApplicationStart; - injectedMetadata: InternalInjectedMetadataStart; -} - // Expands the definition of navigator to include experimental features interface ExtendedNavigator { connection?: { diff --git a/src/core/public/index.ts b/src/core/public/index.ts index e5c137a6d5db..ff666e0f3a18 100644 --- a/src/core/public/index.ts +++ b/src/core/public/index.ts @@ -26,30 +26,19 @@ import './index.scss'; -import type { - InjectedMetadataSetup, - InjectedMetadataStart, -} from '@kbn/core-injected-metadata-browser'; -import { DocLinksStart } from '@kbn/core-doc-links-browser'; -import type { ThemeServiceSetup, ThemeServiceStart } from '@kbn/core-theme-browser'; -import type { AnalyticsServiceSetup, AnalyticsServiceStart } from '@kbn/core-analytics-browser'; -import { ExecutionContextSetup, ExecutionContextStart } from '@kbn/core-execution-context-browser'; -import type { HttpSetup, HttpStart } from '@kbn/core-http-browser'; -import type { I18nStart } from '@kbn/core-i18n-browser'; - -import type { +export type { DocLinksStart } from '@kbn/core-doc-links-browser'; +export type { HttpSetup, HttpStart } from '@kbn/core-http-browser'; +export type { I18nStart } from '@kbn/core-i18n-browser'; +export type { FatalErrorsSetup, FatalErrorsStart, FatalErrorInfo, } from '@kbn/core-fatal-errors-browser'; -import type { UiSettingsState, IUiSettingsClient } from '@kbn/core-ui-settings-browser'; -import type { DeprecationsServiceStart } from '@kbn/core-deprecations-browser'; -import type { Capabilities } from '@kbn/core-capabilities-common'; -import type { OverlayStart } from '@kbn/core-overlays-browser'; -import type { SavedObjectsStart } from '@kbn/core-saved-objects-browser'; -import type { NotificationsSetup, NotificationsStart } from '@kbn/core-notifications-browser'; -import type { ApplicationSetup, ApplicationStart } from '@kbn/core-application-browser'; -import type { +export type { UiSettingsState, IUiSettingsClient } from '@kbn/core-ui-settings-browser'; +export type { Capabilities } from '@kbn/core-capabilities-common'; +export type { SavedObjectsStart } from '@kbn/core-saved-objects-browser'; +export type { NotificationsSetup, NotificationsStart } from '@kbn/core-notifications-browser'; +export type { ChromeBadge, ChromeBreadcrumb, ChromeHelpExtension, @@ -70,19 +59,21 @@ import type { ChromeUserBanner, ChromeHelpMenuActions, } from '@kbn/core-chrome-browser'; -import type { +export type { Plugin, PluginInitializer, PluginInitializerContext, - PluginOpaqueId, -} from './plugins'; +} from '@kbn/core-plugins-browser'; +export type { PluginOpaqueId } from '@kbn/core-base-common'; export type { PackageInfo, EnvironmentMode } from '@kbn/config'; export type { DomainDeprecationDetails } from '@kbn/core-deprecations-common'; export type { CoreContext } from '@kbn/core-base-browser-internal'; -export { DEFAULT_APP_CATEGORIES, APP_WRAPPER_CLASS } from '@kbn/core-application-common'; -export type { CoreSystem } from './core_system'; -export type { AppCategory } from '../types'; +export { + DEFAULT_APP_CATEGORIES, + APP_WRAPPER_CLASS, + type AppCategory, +} from '@kbn/core-application-common'; export type { UiSettingsParams, PublicUiSettingsParams, @@ -233,134 +224,8 @@ export type { ExecutionContextStart, } from '@kbn/core-execution-context-browser'; -/** - * Core services exposed to the `Plugin` setup lifecycle - * - * @typeParam TPluginsStart - the type of the consuming plugin's start dependencies. Should be the same - * as the consuming {@link Plugin}'s `TPluginsStart` type. Used by `getStartServices`. - * @typeParam TStart - the type of the consuming plugin's start contract. Should be the same as the - * consuming {@link Plugin}'s `TStart` type. Used by `getStartServices`. - * - * @public - * - * @internalRemarks We document the properties with \@link tags to improve - * navigation in the generated docs until there's a fix for - * https://github.com/Microsoft/web-build-tools/issues/1237 - */ -export interface CoreSetup { - /** {@link AnalyticsServiceSetup} */ - analytics: AnalyticsServiceSetup; - /** {@link ApplicationSetup} */ - application: ApplicationSetup; - /** {@link FatalErrorsSetup} */ - fatalErrors: FatalErrorsSetup; - /** {@link HttpSetup} */ - http: HttpSetup; - /** {@link NotificationsSetup} */ - notifications: NotificationsSetup; - /** {@link IUiSettingsClient} */ - uiSettings: IUiSettingsClient; - /** {@link ExecutionContextSetup} */ - executionContext: ExecutionContextSetup; - /** {@link InjectedMetadataSetup} */ - injectedMetadata: InjectedMetadataSetup; - /** {@link ThemeServiceSetup} */ - theme: ThemeServiceSetup; - /** {@link StartServicesAccessor} */ - getStartServices: StartServicesAccessor; -} +export type { CoreSetup, CoreStart, StartServicesAccessor } from '@kbn/core-lifecycle-browser'; -/** - * Allows plugins to get access to APIs available in start inside async - * handlers, such as {@link App.mount}. Promise will not resolve until Core - * and plugin dependencies have completed `start`. - * - * @public - */ -export type StartServicesAccessor< - TPluginsStart extends object = object, - TStart = unknown -> = () => Promise<[CoreStart, TPluginsStart, TStart]>; - -/** - * Core services exposed to the `Plugin` start lifecycle - * - * @public - * - * @internalRemarks We document the properties with \@link tags to improve - * navigation in the generated docs until there's a fix for - * https://github.com/Microsoft/web-build-tools/issues/1237 - */ -export interface CoreStart { - /** {@link AnalyticsServiceStart} */ - analytics: AnalyticsServiceStart; - /** {@link ApplicationStart} */ - application: ApplicationStart; - /** {@link ChromeStart} */ - chrome: ChromeStart; - /** {@link DocLinksStart} */ - docLinks: DocLinksStart; - /** {@link ExecutionContextStart} */ - executionContext: ExecutionContextStart; - /** {@link HttpStart} */ - http: HttpStart; - /** {@link SavedObjectsStart} */ - savedObjects: SavedObjectsStart; - /** {@link I18nStart} */ - i18n: I18nStart; - /** {@link NotificationsStart} */ - notifications: NotificationsStart; - /** {@link OverlayStart} */ - overlays: OverlayStart; - /** {@link IUiSettingsClient} */ - uiSettings: IUiSettingsClient; - /** {@link FatalErrorsStart} */ - fatalErrors: FatalErrorsStart; - /** {@link DeprecationsServiceStart} */ - deprecations: DeprecationsServiceStart; - /** {@link ThemeServiceStart} */ - theme: ThemeServiceStart; - /** {@link InjectedMetadataStart} */ - injectedMetadata: InjectedMetadataStart; -} - -export type { - Capabilities, - ChromeBadge, - ChromeBreadcrumb, - ChromeHelpExtension, - ChromeHelpMenuActions, - ChromeHelpExtensionMenuLink, - ChromeHelpExtensionLinkBase, - ChromeHelpExtensionMenuCustomLink, - ChromeHelpExtensionMenuDiscussLink, - ChromeHelpExtensionMenuDocumentationLink, - ChromeHelpExtensionMenuGitHubLink, - ChromeNavControl, - ChromeNavControls, - ChromeNavLink, - ChromeNavLinks, - ChromeDocTitle, - ChromeRecentlyAccessed, - ChromeRecentlyAccessedHistoryItem, - ChromeUserBanner, - ChromeStart, - DocLinksStart, - FatalErrorInfo, - FatalErrorsSetup, - FatalErrorsStart, - HttpSetup, - HttpStart, - I18nStart, - NotificationsSetup, - NotificationsStart, - Plugin, - PluginInitializer, - PluginInitializerContext, - SavedObjectsStart, - PluginOpaqueId, - IUiSettingsClient, - UiSettingsState, -}; +export type { CoreSystem } from './core_system'; export { __kbnBootstrap__ } from './kbn_bootstrap'; diff --git a/src/core/public/mocks.ts b/src/core/public/mocks.ts index 82c566243427..790b8ccf028c 100644 --- a/src/core/public/mocks.ts +++ b/src/core/public/mocks.ts @@ -7,25 +7,12 @@ */ import { createMemoryHistory } from 'history'; -import { injectedMetadataServiceMock } from '@kbn/core-injected-metadata-browser-mocks'; -import { docLinksServiceMock } from '@kbn/core-doc-links-browser-mocks'; import { themeServiceMock } from '@kbn/core-theme-browser-mocks'; import { coreContextMock } from '@kbn/core-base-browser-mocks'; -import { analyticsServiceMock } from '@kbn/core-analytics-browser-mocks'; -import { executionContextServiceMock } from '@kbn/core-execution-context-browser-mocks'; -import { i18nServiceMock } from '@kbn/core-i18n-browser-mocks'; -import { fatalErrorsServiceMock } from '@kbn/core-fatal-errors-browser-mocks'; -import { httpServiceMock } from '@kbn/core-http-browser-mocks'; -import { uiSettingsServiceMock } from '@kbn/core-ui-settings-browser-mocks'; -import { deprecationsServiceMock } from '@kbn/core-deprecations-browser-mocks'; -import { overlayServiceMock } from '@kbn/core-overlays-browser-mocks'; -import { savedObjectsServiceMock } from '@kbn/core-saved-objects-browser-mocks'; -import { notificationServiceMock } from '@kbn/core-notifications-browser-mocks'; -import { applicationServiceMock } from '@kbn/core-application-browser-mocks'; import { CoreScopedHistory } from '@kbn/core-application-browser-internal'; import type { AppMountParameters } from '@kbn/core-application-browser'; -import { chromeServiceMock } from '@kbn/core-chrome-browser-mocks'; -import type { PluginInitializerContext } from '.'; +import { pluginsServiceMock } from '@kbn/core-plugins-browser-mocks'; +import { coreLifecycleMock } from '@kbn/core-lifecycle-browser-mocks'; export { injectedMetadataServiceMock } from '@kbn/core-injected-metadata-browser-mocks'; export { docLinksServiceMock } from '@kbn/core-doc-links-browser-mocks'; @@ -46,86 +33,6 @@ export { export { applicationServiceMock, scopedHistoryMock } from '@kbn/core-application-browser-mocks'; export { deprecationsServiceMock } from '@kbn/core-deprecations-browser-mocks'; -function createCoreSetupMock({ - basePath = '', - pluginStartDeps = {}, - pluginStartContract, -}: { - basePath?: string; - pluginStartDeps?: object; - pluginStartContract?: any; -} = {}) { - const mock = { - analytics: analyticsServiceMock.createAnalyticsServiceSetup(), - application: applicationServiceMock.createSetupContract(), - docLinks: docLinksServiceMock.createSetupContract(), - executionContext: executionContextServiceMock.createSetupContract(), - fatalErrors: fatalErrorsServiceMock.createSetupContract(), - getStartServices: jest.fn, any, any]>, []>(() => - Promise.resolve([createCoreStartMock({ basePath }), pluginStartDeps, pluginStartContract]) - ), - http: httpServiceMock.createSetupContract({ basePath }), - notifications: notificationServiceMock.createSetupContract(), - uiSettings: uiSettingsServiceMock.createSetupContract(), - deprecations: deprecationsServiceMock.createSetupContract(), - injectedMetadata: { - getInjectedVar: injectedMetadataServiceMock.createSetupContract().getInjectedVar, - }, - theme: themeServiceMock.createSetupContract(), - }; - - return mock; -} - -function createCoreStartMock({ basePath = '' } = {}) { - const mock = { - analytics: analyticsServiceMock.createAnalyticsServiceStart(), - application: applicationServiceMock.createStartContract(), - chrome: chromeServiceMock.createStartContract(), - docLinks: docLinksServiceMock.createStartContract(), - executionContext: executionContextServiceMock.createStartContract(), - http: httpServiceMock.createStartContract({ basePath }), - i18n: i18nServiceMock.createStartContract(), - notifications: notificationServiceMock.createStartContract(), - overlays: overlayServiceMock.createStartContract(), - uiSettings: uiSettingsServiceMock.createStartContract(), - savedObjects: savedObjectsServiceMock.createStartContract(), - deprecations: deprecationsServiceMock.createStartContract(), - theme: themeServiceMock.createStartContract(), - injectedMetadata: { - getInjectedVar: injectedMetadataServiceMock.createStartContract().getInjectedVar, - }, - fatalErrors: fatalErrorsServiceMock.createStartContract(), - }; - - return mock; -} - -function pluginInitializerContextMock(config: any = {}) { - const mock: PluginInitializerContext = { - opaqueId: Symbol(), - env: { - mode: { - dev: true, - name: 'development', - prod: false, - }, - packageInfo: { - version: 'version', - branch: 'branch', - buildNum: 100, - buildSha: 'buildSha', - dist: false, - }, - }, - config: { - get: () => config as T, - }, - }; - - return mock; -} - function createStorageMock() { const storageMock: jest.Mocked = { getItem: jest.fn(), @@ -158,9 +65,9 @@ function createAppMountParametersMock(appBasePath = '') { export const coreMock = { createCoreContext: coreContextMock.create, - createSetup: createCoreSetupMock, - createStart: createCoreStartMock, - createPluginInitializerContext: pluginInitializerContextMock, + createSetup: coreLifecycleMock.createCoreSetup, + createStart: coreLifecycleMock.createCoreStart, + createPluginInitializerContext: pluginsServiceMock.createPluginInitializerContext, createStorage: createStorageMock, createAppMountParameters: createAppMountParametersMock, }; diff --git a/src/plugins/data/common/search/aggs/metrics/std_deviation.test.ts b/src/plugins/data/common/search/aggs/metrics/std_deviation.test.ts index 38e0ca8c3975..3e9ba6c9ecfa 100644 --- a/src/plugins/data/common/search/aggs/metrics/std_deviation.test.ts +++ b/src/plugins/data/common/search/aggs/metrics/std_deviation.test.ts @@ -13,7 +13,10 @@ import { METRIC_TYPES } from './metric_agg_types'; describe('AggTypeMetricStandardDeviationProvider class', () => { const typesRegistry = mockAggTypesRegistry(); - const getAggConfigs = (customLabel?: string) => { + const getAggConfigs = ({ + customLabel, + showBounds, + }: { customLabel?: string; showBounds?: boolean } = {}) => { const field = { name: 'memory', }; @@ -38,6 +41,7 @@ describe('AggTypeMetricStandardDeviationProvider class', () => { displayName: 'memory', }, customLabel, + ...(showBounds != null ? { showBounds } : {}), }, }, ], @@ -47,7 +51,7 @@ describe('AggTypeMetricStandardDeviationProvider class', () => { }; it('uses the custom label if it is set', () => { - const aggConfigs = getAggConfigs('custom label'); + const aggConfigs = getAggConfigs({ customLabel: 'custom label' }); const responseAggs: any = getStdDeviationMetricAgg().getResponseAggs( aggConfigs.aggs[0] as IStdDevAggConfig ); @@ -102,4 +106,12 @@ describe('AggTypeMetricStandardDeviationProvider class', () => { } `); }); + + it('returns null without throwing if no "extended_stats" is returned', () => { + const aggConfigs = getAggConfigs({ showBounds: false }); + + expect(() => + getStdDeviationMetricAgg().getValue(aggConfigs.aggs[0] as IStdDevAggConfig, {}) + ).not.toThrow(); + }); }); diff --git a/src/plugins/data/common/search/aggs/metrics/std_deviation.ts b/src/plugins/data/common/search/aggs/metrics/std_deviation.ts index 8bc1af26509d..c64ba8ed22c6 100644 --- a/src/plugins/data/common/search/aggs/metrics/std_deviation.ts +++ b/src/plugins/data/common/search/aggs/metrics/std_deviation.ts @@ -117,7 +117,7 @@ export const getStdDeviationMetricAgg = () => { getValue(agg, bucket) { const showBounds = agg.getParam('showBounds'); if (showBounds === false) { - return bucket[agg.id].std_deviation; + return bucket[agg.id]?.std_deviation; } return get(bucket[agg.parentId], agg.valProp()); }, diff --git a/test/functional/apps/dashboard_elements/controls/options_list.ts b/test/functional/apps/dashboard_elements/controls/options_list.ts index d6f49b9b435e..56c1778d31de 100644 --- a/test/functional/apps/dashboard_elements/controls/options_list.ts +++ b/test/functional/apps/dashboard_elements/controls/options_list.ts @@ -75,7 +75,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); }); - describe('Options List Control creation and editing experience', async () => { + // Skip on cloud until issue is fixed + // Issue: https://github.com/elastic/kibana/issues/141280 + describe('Options List Control creation and editing experience', function () { + this.tags(['skipCloudFailedTest']); it('can add a new options list control from a blank state', async () => { await dashboardControls.createControl({ controlType: OPTIONS_LIST_CONTROL, diff --git a/test/functional/services/common/find.ts b/test/functional/services/common/find.ts index ec2d5385a0a4..a7c782b23686 100644 --- a/test/functional/services/common/find.ts +++ b/test/functional/services/common/find.ts @@ -90,6 +90,26 @@ export class FindService extends FtrService { }); } + public async setValueByClass(selector: string, text: string, topOffset?: number): Promise { + this.log.debug(`Find.setValueByClass('${selector}', '${text}')`); + return await this.retry.try(async () => { + const element = await this.byClassName(selector); + await element.click(topOffset); + + // in case the input element is actually a child of the testSubject, we + // call clearValue() and type() on the element that is focused after + // clicking on the testSubject + const input = await this.activeElement(); + if (input) { + await input.clearValue(); + await input.type(text); + } else { + await element.clearValue(); + await element.type(text); + } + }); + } + public async selectValue(selector: string, value: string): Promise { this.log.debug(`Find.selectValue('${selector}', option[value="${value}"]')`); const combobox = await this.byCssSelector(selector); diff --git a/x-pack/plugins/aiops/public/application/utils/query_utils.test.ts b/x-pack/plugins/aiops/public/application/utils/query_utils.test.ts new file mode 100644 index 000000000000..59d680d236b1 --- /dev/null +++ b/x-pack/plugins/aiops/public/application/utils/query_utils.test.ts @@ -0,0 +1,254 @@ +/* + * 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 type { ChangePoint } from '@kbn/ml-agg-utils'; + +import type { GroupTableItem } from '../../components/spike_analysis_table/spike_analysis_table_groups'; + +import { buildBaseFilterCriteria } from './query_utils'; + +const selectedChangePointMock: ChangePoint = { + doc_count: 53408, + bg_count: 1154, + fieldName: 'meta.cloud.instance_id.keyword', + fieldValue: '1234', + normalizedScore: 1, + pValue: 0.01, + score: 708.3964185322641, + total_bg_count: 179657, + total_doc_count: 114011, +}; + +const selectedGroupMock: GroupTableItem = { + id: '21289599', + docCount: 20468, + pValue: 2.2250738585072626e-308, + group: { + 'error.message': 'rate limit exceeded', + message: 'too many requests', + 'user_agent.original.keyword': 'Mozilla/5.0', + }, + repeatedValues: { + 'beat.hostname.keyword': 'ip-192-168-1-1', + 'beat.name.keyword': 'i-1234', + 'docker.container.id.keyword': 'asdf', + }, + histogram: [], +}; + +describe('query_utils', () => { + describe('buildBaseFilterCriteria', () => { + it('returns range filter based on minimum supplied arguments', () => { + const baseFilterCriteria = buildBaseFilterCriteria('the-time-field-name', 1234, 5678); + + expect(baseFilterCriteria).toEqual([ + { + range: { + 'the-time-field-name': { + format: 'epoch_millis', + gte: 1234, + lte: 5678, + }, + }, + }, + ]); + }); + + it('returns filters including default query with supplied arguments provided via UI', () => { + const baseFilterCriteria = buildBaseFilterCriteria( + '@timestamp', + 1640082000012, + 1640103600906, + { match_all: {} } + ); + + expect(baseFilterCriteria).toEqual([ + { + range: { + '@timestamp': { + format: 'epoch_millis', + gte: 1640082000012, + lte: 1640103600906, + }, + }, + }, + { match_all: {} }, + ]); + }); + + it('includes a term filter when including a selectedChangePoint', () => { + const baseFilterCriteria = buildBaseFilterCriteria( + '@timestamp', + 1640082000012, + 1640103600906, + { match_all: {} }, + selectedChangePointMock + ); + + expect(baseFilterCriteria).toEqual([ + { + range: { + '@timestamp': { + format: 'epoch_millis', + gte: 1640082000012, + lte: 1640103600906, + }, + }, + }, + { match_all: {} }, + { term: { 'meta.cloud.instance_id.keyword': '1234' } }, + ]); + }); + + it('includes a term filter with must_not when excluding a selectedChangePoint', () => { + const baseFilterCriteria = buildBaseFilterCriteria( + '@timestamp', + 1640082000012, + 1640103600906, + { match_all: {} }, + selectedChangePointMock, + false + ); + + expect(baseFilterCriteria).toEqual([ + { + range: { + '@timestamp': { + format: 'epoch_millis', + gte: 1640082000012, + lte: 1640103600906, + }, + }, + }, + { match_all: {} }, + { bool: { must_not: [{ term: { 'meta.cloud.instance_id.keyword': '1234' } }] } }, + ]); + }); + + it('includes multiple term filters when including a selectedGroupMock', () => { + const baseFilterCriteria = buildBaseFilterCriteria( + '@timestamp', + 1640082000012, + 1640103600906, + { match_all: {} }, + undefined, + true, + selectedGroupMock + ); + + expect(baseFilterCriteria).toEqual([ + { + range: { + '@timestamp': { + format: 'epoch_millis', + gte: 1640082000012, + lte: 1640103600906, + }, + }, + }, + { match_all: {} }, + { + term: { + 'error.message': 'rate limit exceeded', + }, + }, + { + term: { + message: 'too many requests', + }, + }, + { + term: { + 'user_agent.original.keyword': 'Mozilla/5.0', + }, + }, + { + term: { + 'beat.hostname.keyword': 'ip-192-168-1-1', + }, + }, + { + term: { + 'beat.name.keyword': 'i-1234', + }, + }, + { + term: { + 'docker.container.id.keyword': 'asdf', + }, + }, + ]); + }); + + it('includes a must_not with nested term filters when excluding a selectedGroup', () => { + const baseFilterCriteria = buildBaseFilterCriteria( + '@timestamp', + 1640082000012, + 1640103600906, + { match_all: {} }, + undefined, + false, + selectedGroupMock + ); + + expect(baseFilterCriteria).toEqual([ + { + range: { + '@timestamp': { + format: 'epoch_millis', + gte: 1640082000012, + lte: 1640103600906, + }, + }, + }, + { match_all: {} }, + { + bool: { + must_not: [ + { + bool: { + filter: [ + { + term: { + 'error.message': 'rate limit exceeded', + }, + }, + { + term: { + message: 'too many requests', + }, + }, + { + term: { + 'user_agent.original.keyword': 'Mozilla/5.0', + }, + }, + { + term: { + 'beat.hostname.keyword': 'ip-192-168-1-1', + }, + }, + { + term: { + 'beat.name.keyword': 'i-1234', + }, + }, + { + term: { + 'docker.container.id.keyword': 'asdf', + }, + }, + ], + }, + }, + ], + }, + }, + ]); + }); + }); +}); diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/rules/error_count.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/rules/error_count.cy.ts index ec01fc2df2da..a9ba45b4ad31 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/rules/error_count.cy.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/rules/error_count.cy.ts @@ -5,6 +5,9 @@ * 2.0. */ +import { synthtrace } from '../../../../synthtrace'; +import { generateData } from './generate_data'; + function deleteAllRules() { cy.log('Delete all rules'); cy.request({ @@ -38,6 +41,21 @@ describe('Rules', () => { deleteAllRules(); }); + before(() => { + const start = '2021-10-10T00:00:00.000Z'; + const end = '2021-10-10T00:01:00.000Z'; + synthtrace.index( + generateData({ + from: new Date(start).getTime(), + to: new Date(end).getTime(), + }) + ); + }); + + after(() => { + synthtrace.clean(); + }); + describe('Error count', () => { const ruleName = 'Error count threshold'; const comboBoxInputSelector = diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/rules/generate_data.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/rules/generate_data.ts new file mode 100644 index 000000000000..880c9d6b5944 --- /dev/null +++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/rules/generate_data.ts @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { apm, timerange } from '@kbn/apm-synthtrace'; + +export function generateData({ from, to }: { from: number; to: number }) { + const range = timerange(from, to); + + const opbeansJava = apm + .service({ + name: 'opbeans-java', + environment: 'production', + agentName: 'java', + }) + .instance('opbeans-java-prod-1') + .podId('opbeans-java-prod-1-pod'); + + const opbeansNode = apm + .service({ + name: 'opbeans-node', + environment: 'production', + agentName: 'nodejs', + }) + .instance('opbeans-node-prod-1'); + + return range + .interval('2m') + .rate(1) + .generator((timestamp, index) => [ + opbeansJava + .transaction({ transactionName: 'GET /apple 🍎 ' }) + .timestamp(timestamp) + .duration(1000) + .success() + .errors( + opbeansJava + .error({ message: `Error ${index}`, type: `exception ${index}` }) + .timestamp(timestamp) + ), + opbeansNode + .transaction({ transactionName: 'GET /banana 🍌' }) + .timestamp(timestamp) + .duration(500) + .success(), + ]); +} diff --git a/x-pack/plugins/apm/ftr_e2e/cypress_test_runner.ts b/x-pack/plugins/apm/ftr_e2e/cypress_test_runner.ts index 9736a695e81c..8af466b87262 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress_test_runner.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress_test_runner.ts @@ -73,7 +73,7 @@ export async function cypressTestRunner({ getService }: FtrProviderContext) { return res; } -function getCypressCliArgs() { +function getCypressCliArgs(): Record { if (!process.env.CYPRESS_CLI_ARGS) { return {}; } @@ -82,5 +82,11 @@ function getCypressCliArgs() { process.env.CYPRESS_CLI_ARGS ) as Record; - return cypressCliArgs; + const spec = + typeof cypressCliArgs.spec === 'string' && + !cypressCliArgs.spec.includes('**') + ? `**/${cypressCliArgs.spec}*` + : cypressCliArgs.spec; + + return { ...cypressCliArgs, spec }; } diff --git a/x-pack/plugins/apm/public/components/app/service_inventory/service_inventory.stories.tsx b/x-pack/plugins/apm/public/components/app/service_inventory/service_inventory.stories.tsx index 2cd79535afac..9d547f228830 100644 --- a/x-pack/plugins/apm/public/components/app/service_inventory/service_inventory.stories.tsx +++ b/x-pack/plugins/apm/public/components/app/service_inventory/service_inventory.stories.tsx @@ -40,7 +40,7 @@ const stories: Meta<{}> = { }, notifications: { toasts: { add: () => {}, addWarning: () => {} } }, uiSettings: { get: () => [] }, - dataViews: { get: async () => {} }, + dataViews: { create: async () => {} }, } as unknown as CoreStart; const KibanaReactContext = createKibanaReactContext(coreMock); diff --git a/x-pack/plugins/apm/public/components/routing/templates/apm_main_template.tsx b/x-pack/plugins/apm/public/components/routing/templates/apm_main_template.tsx index ff8442016adc..4eb7d8140fdf 100644 --- a/x-pack/plugins/apm/public/components/routing/templates/apm_main_template.tsx +++ b/x-pack/plugins/apm/public/components/routing/templates/apm_main_template.tsx @@ -48,7 +48,7 @@ export function ApmMainTemplate({ const location = useLocation(); const { services } = useKibana(); - const { http, docLinks, observability } = services; + const { http, docLinks, observability, application } = services; const basePath = http?.basePath.get(); const ObservabilityPageTemplate = observability.navigation.PageTemplate; @@ -57,6 +57,19 @@ export function ApmMainTemplate({ return callApmApi('GET /internal/apm/has_data'); }, []); + // create static data view on inital load + useFetcher( + (callApmApi) => { + const canCreateDataView = + application?.capabilities.savedObjectsManagement.edit; + + if (canCreateDataView) { + return callApmApi('POST /internal/apm/data_view/static'); + } + }, + [application?.capabilities.savedObjectsManagement.edit] + ); + const shouldBypassNoDataScreen = bypassNoDataScreenPaths.some((path) => location.pathname.includes(path) ); diff --git a/x-pack/plugins/apm/public/components/shared/search_bar.test.tsx b/x-pack/plugins/apm/public/components/shared/search_bar.test.tsx index 33a3646bf752..1fb1e15a26ee 100644 --- a/x-pack/plugins/apm/public/components/shared/search_bar.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/search_bar.test.tsx @@ -15,6 +15,7 @@ import { ApmServiceContextProvider } from '../../context/apm_service/apm_service import { UrlParamsProvider } from '../../context/url_params_context/url_params_context'; import type { ApmUrlParams } from '../../context/url_params_context/types'; import * as useFetcherHook from '../../hooks/use_fetcher'; +import * as useApmDataViewHook from '../../hooks/use_apm_data_view'; import * as useServiceTransactionTypesHook from '../../context/apm_service/use_service_transaction_types_fetcher'; import { renderWithTheme } from '../../utils/test_helpers'; import { fromQuery } from './links/url_helpers'; @@ -45,6 +46,11 @@ function setup({ .spyOn(useServiceTransactionTypesHook, 'useServiceTransactionTypesFetcher') .mockReturnValue(serviceTransactionTypes); + // mock transaction types + jest + .spyOn(useApmDataViewHook, 'useApmDataView') + .mockReturnValue({ dataView: undefined }); + jest.spyOn(useFetcherHook, 'useFetcher').mockReturnValue({} as any); return renderWithTheme( diff --git a/x-pack/plugins/apm/public/hooks/use_apm_data_view.ts b/x-pack/plugins/apm/public/hooks/use_apm_data_view.ts index 9d4ef214ff5e..63d7232128d8 100644 --- a/x-pack/plugins/apm/public/hooks/use_apm_data_view.ts +++ b/x-pack/plugins/apm/public/hooks/use_apm_data_view.ts @@ -7,20 +7,10 @@ import { DataView } from '@kbn/data-views-plugin/common'; import { useKibana } from '@kbn/kibana-react-plugin/public'; -import { SavedObjectNotFound } from '@kbn/kibana-utils-plugin/common'; import { useEffect, useState } from 'react'; -import { APM_STATIC_DATA_VIEW_ID } from '../../common/data_view_constants'; -import { useApmPluginContext } from '../context/apm_plugin/use_apm_plugin_context'; import { ApmPluginStartDeps } from '../plugin'; import { callApmApi } from '../services/rest/create_call_apm_api'; -async function createStaticApmDataView() { - const res = await callApmApi('POST /internal/apm/data_view/static', { - signal: null, - }); - return res.dataView; -} - async function getApmDataViewTitle() { const res = await callApmApi('GET /internal/apm/data_view/title', { signal: null, @@ -30,37 +20,16 @@ async function getApmDataViewTitle() { export function useApmDataView() { const { services } = useKibana(); - const { core } = useApmPluginContext(); const [dataView, setDataView] = useState(); - const canCreateDataView = - core.application.capabilities.savedObjectsManagement.edit; - useEffect(() => { async function fetchDataView() { - try { - // load static data view - return await services.dataViews.get(APM_STATIC_DATA_VIEW_ID); - } catch (e) { - // re-throw if an unhandled error occurred - const notFound = e instanceof SavedObjectNotFound; - if (!notFound) { - throw e; - } - - // create static data view if user has permissions - if (canCreateDataView) { - return createStaticApmDataView(); - } else { - // or create dynamic data view if user does not have permissions to create a static - const title = await getApmDataViewTitle(); - return services.dataViews.create({ title }); - } - } + const title = await getApmDataViewTitle(); + return services.dataViews.create({ title }); } - fetchDataView().then((dv) => setDataView(dv)); - }, [canCreateDataView, services.dataViews]); + fetchDataView().then(setDataView); + }, [services.dataViews]); return { dataView }; } diff --git a/x-pack/plugins/apm/server/lib/helpers/setup_request.ts b/x-pack/plugins/apm/server/lib/helpers/setup_request.ts index 9d6a716c8b3e..dc6e37936740 100644 --- a/x-pack/plugins/apm/server/lib/helpers/setup_request.ts +++ b/x-pack/plugins/apm/server/lib/helpers/setup_request.ts @@ -39,7 +39,7 @@ export async function setupRequest({ plugins, request, config, -}: APMRouteHandlerResources) { +}: APMRouteHandlerResources): Promise { return withApmSpan('setup_request', async () => { const { query } = params; const coreContext = await context.core; diff --git a/x-pack/plugins/apm/server/routes/data_view/create_static_data_view.test.ts b/x-pack/plugins/apm/server/routes/data_view/create_static_data_view.test.ts index c7e22eba054b..65ecb93bcb76 100644 --- a/x-pack/plugins/apm/server/routes/data_view/create_static_data_view.test.ts +++ b/x-pack/plugins/apm/server/routes/data_view/create_static_data_view.test.ts @@ -8,8 +8,9 @@ import { createStaticDataView } from './create_static_data_view'; import { Setup } from '../../lib/helpers/setup_request'; import * as HistoricalAgentData from '../historical_data/has_historical_agent_data'; -import { APMConfig } from '../..'; import { DataViewsService } from '@kbn/data-views-plugin/common'; +import { APMRouteHandlerResources, APMCore } from '../typings'; +import { APMConfig } from '../..'; function getMockedDataViewService(existingDataViewTitle: string) { return { @@ -20,7 +21,7 @@ function getMockedDataViewService(existingDataViewTitle: string) { } as unknown as DataViewsService; } -const setup = { +const setupMock = { indices: { transaction: 'apm-*-transaction-*', span: 'apm-*-span-*', @@ -29,12 +30,28 @@ const setup = { } as APMConfig['indices'], } as unknown as Setup; +const coreMock = { + start: () => { + return { + savedObjects: { + getScopedClient: () => { + return { + updateObjectsSpaces: () => {}, + }; + }, + }, + }; + }, +} as unknown as APMCore; + describe('createStaticDataView', () => { it(`should not create data view if 'xpack.apm.autocreateApmIndexPattern=false'`, async () => { const dataViewService = getMockedDataViewService('apm-*'); await createStaticDataView({ - setup, - config: { autoCreateApmDataView: false } as APMConfig, + setup: setupMock, + resources: { + config: { autoCreateApmDataView: false }, + } as APMRouteHandlerResources, dataViewService, }); expect(dataViewService.createAndSave).not.toHaveBeenCalled(); @@ -49,8 +66,10 @@ describe('createStaticDataView', () => { const dataViewService = getMockedDataViewService('apm-*'); await createStaticDataView({ - setup, - config: { autoCreateApmDataView: true } as APMConfig, + setup: setupMock, + resources: { + config: { autoCreateApmDataView: false }, + } as APMRouteHandlerResources, dataViewService, }); expect(dataViewService.createAndSave).not.toHaveBeenCalled(); @@ -65,8 +84,11 @@ describe('createStaticDataView', () => { const dataViewService = getMockedDataViewService('apm-*'); await createStaticDataView({ - setup, - config: { autoCreateApmDataView: true } as APMConfig, + setup: setupMock, + resources: { + core: coreMock, + config: { autoCreateApmDataView: true }, + } as APMRouteHandlerResources, dataViewService, }); @@ -84,8 +106,11 @@ describe('createStaticDataView', () => { 'apm-*-transaction-*,apm-*-span-*,apm-*-error-*,apm-*-metrics-*'; await createStaticDataView({ - setup, - config: { autoCreateApmDataView: true } as APMConfig, + setup: setupMock, + resources: { + core: coreMock, + config: { autoCreateApmDataView: true }, + } as APMRouteHandlerResources, dataViewService, }); @@ -110,8 +135,11 @@ describe('createStaticDataView', () => { ); await createStaticDataView({ - setup, - config: { autoCreateApmDataView: true } as APMConfig, + setup: setupMock, + resources: { + core: coreMock, + config: { autoCreateApmDataView: true }, + } as APMRouteHandlerResources, dataViewService, }); diff --git a/x-pack/plugins/apm/server/routes/data_view/create_static_data_view.ts b/x-pack/plugins/apm/server/routes/data_view/create_static_data_view.ts index 6142791b45cc..c2310acadcff 100644 --- a/x-pack/plugins/apm/server/routes/data_view/create_static_data_view.ts +++ b/x-pack/plugins/apm/server/routes/data_view/create_static_data_view.ts @@ -7,6 +7,7 @@ import { SavedObjectsErrorHelpers } from '@kbn/core/server'; import { DataView, DataViewsService } from '@kbn/data-views-plugin/common'; +import { i18n } from '@kbn/i18n'; import { TRACE_ID, TRANSACTION_ID, @@ -15,29 +16,49 @@ import { APM_STATIC_DATA_VIEW_ID } from '../../../common/data_view_constants'; import { hasHistoricalAgentData } from '../historical_data/has_historical_agent_data'; import { withApmSpan } from '../../utils/with_apm_span'; import { getApmDataViewTitle } from './get_apm_data_view_title'; + +import { APMRouteHandlerResources } from '../typings'; import { Setup } from '../../lib/helpers/setup_request'; -import { APMConfig } from '../..'; + +export type CreateDataViewResponse = Promise< + | { created: boolean; dataView: DataView } + | { created: boolean; reason?: string } +>; export async function createStaticDataView({ dataViewService, - config, + resources, setup, }: { dataViewService: DataViewsService; - config: APMConfig; + resources: APMRouteHandlerResources; setup: Setup; -}): Promise { +}): CreateDataViewResponse { + const { config } = resources; + return withApmSpan('create_static_data_view', async () => { // don't auto-create APM data view if it's been disabled via the config if (!config.autoCreateApmDataView) { - return; + return { + created: false, + reason: i18n.translate('xpack.apm.dataView.autoCreateDisabled', { + defaultMessage: + 'Auto-creation of data views has been disabled via "autoCreateApmDataView" config option', + }), + }; } // Discover and other apps will throw errors if an data view exists without having matching indices. // The following ensures the data view is only created if APM data is found const hasData = await hasHistoricalAgentData(setup); + if (!hasData) { - return; + return { + created: false, + reason: i18n.translate('xpack.apm.dataView.noApmData', { + defaultMessage: 'No APM data', + }), + }; } const apmDataViewTitle = getApmDataViewTitle(setup.indices); @@ -47,50 +68,42 @@ export async function createStaticDataView({ }); if (!shouldCreateOrUpdate) { - return; + return { + created: false, + reason: i18n.translate( + 'xpack.apm.dataView.alreadyExistsInActiveSpace', + { defaultMessage: 'Dataview already exists in the active space' } + ), + }; } - try { - return await withApmSpan('create_data_view', async () => { - const dataView = await dataViewService.createAndSave( - { - allowNoIndex: true, - id: APM_STATIC_DATA_VIEW_ID, - name: 'APM', - title: apmDataViewTitle, - timeFieldName: '@timestamp', - - // link to APM from Discover - fieldFormats: { - [TRACE_ID]: { - id: 'url', - params: { - urlTemplate: 'apm/link-to/trace/{{value}}', - labelTemplate: '{{value}}', - }, - }, - [TRANSACTION_ID]: { - id: 'url', - params: { - urlTemplate: 'apm/link-to/transaction/{{value}}', - labelTemplate: '{{value}}', - }, - }, - }, - }, - true - ); - - return dataView; - }); - } catch (e) { - // if the data view (saved object) already exists a conflict error (code: 409) will be thrown - // that error should be silenced - if (SavedObjectsErrorHelpers.isConflictError(e)) { - return; + return await withApmSpan('create_data_view', async () => { + try { + const dataView = await createAndSaveStaticDataView({ + dataViewService, + apmDataViewTitle, + }); + + await addDataViewToAllSpaces(resources); + + return { created: true, dataView }; + } catch (e) { + // if the data view (saved object) already exists a conflict error (code: 409) will be thrown + if (SavedObjectsErrorHelpers.isConflictError(e)) { + return { + created: false, + reason: i18n.translate( + 'xpack.apm.dataView.alreadyExistsInAnotherSpace', + { + defaultMessage: + 'Dataview already exists in another space but is not made available in this space', + } + ), + }; + } + throw e; } - throw e; - } + }); }); } @@ -114,3 +127,53 @@ async function getShouldCreateOrUpdate({ throw e; } } + +async function addDataViewToAllSpaces(resources: APMRouteHandlerResources) { + const { request, core } = resources; + const startServices = await core.start(); + const scopedClient = startServices.savedObjects.getScopedClient(request); + + // make data view available across all spaces + return scopedClient.updateObjectsSpaces( + [{ id: APM_STATIC_DATA_VIEW_ID, type: 'index-pattern' }], + ['*'], + [] + ); +} + +function createAndSaveStaticDataView({ + dataViewService, + apmDataViewTitle, +}: { + dataViewService: DataViewsService; + apmDataViewTitle: string; +}) { + return dataViewService.createAndSave( + { + allowNoIndex: true, + id: APM_STATIC_DATA_VIEW_ID, + name: 'APM', + title: apmDataViewTitle, + timeFieldName: '@timestamp', + + // link to APM from Discover + fieldFormats: { + [TRACE_ID]: { + id: 'url', + params: { + urlTemplate: 'apm/link-to/trace/{{value}}', + labelTemplate: '{{value}}', + }, + }, + [TRANSACTION_ID]: { + id: 'url', + params: { + urlTemplate: 'apm/link-to/transaction/{{value}}', + labelTemplate: '{{value}}', + }, + }, + }, + }, + true + ); +} diff --git a/x-pack/plugins/apm/server/routes/data_view/route.ts b/x-pack/plugins/apm/server/routes/data_view/route.ts index a8c660c293d4..6f55f67fc9b4 100644 --- a/x-pack/plugins/apm/server/routes/data_view/route.ts +++ b/x-pack/plugins/apm/server/routes/data_view/route.ts @@ -5,21 +5,23 @@ * 2.0. */ -import { DataView } from '@kbn/data-views-plugin/common'; -import { createStaticDataView } from './create_static_data_view'; -import { setupRequest } from '../../lib/helpers/setup_request'; +import { + CreateDataViewResponse, + createStaticDataView, +} from './create_static_data_view'; import { createApmServerRoute } from '../apm_routes/create_apm_server_route'; import { getApmDataViewTitle } from './get_apm_data_view_title'; import { getApmIndices } from '../settings/apm_indices/get_apm_indices'; +import { setupRequest } from '../../lib/helpers/setup_request'; const staticDataViewRoute = createApmServerRoute({ endpoint: 'POST /internal/apm/data_view/static', options: { tags: ['access:apm'] }, - handler: async (resources): Promise<{ dataView: DataView | undefined }> => { + handler: async (resources): CreateDataViewResponse => { + const { context, plugins, request } = resources; const setup = await setupRequest(resources); - const { context, plugins, request, config } = resources; - const coreContext = await context.core; + const dataViewStart = await plugins.dataViews.start(); const dataViewService = await dataViewStart.dataViewsServiceFactory( coreContext.savedObjects.client, @@ -28,13 +30,13 @@ const staticDataViewRoute = createApmServerRoute({ true ); - const dataView = await createStaticDataView({ + const res = await createStaticDataView({ dataViewService, - config, + resources, setup, }); - return { dataView }; + return res; }, }); diff --git a/x-pack/plugins/apm/server/routes/typings.ts b/x-pack/plugins/apm/server/routes/typings.ts index 2e246f753d75..ff75af8f8334 100644 --- a/x-pack/plugins/apm/server/routes/typings.ts +++ b/x-pack/plugins/apm/server/routes/typings.ts @@ -47,6 +47,11 @@ export type TelemetryUsageCounter = ReturnType< UsageCollectionSetup['createUsageCounter'] >; +export interface APMCore { + setup: CoreSetup; + start: () => Promise; +} + export interface APMRouteHandlerResources { request: KibanaRequest; context: ApmPluginRequestHandlerContext; @@ -59,10 +64,7 @@ export interface APMRouteHandlerResources { }; config: APMConfig; logger: Logger; - core: { - setup: CoreSetup; - start: () => Promise; - }; + core: APMCore; plugins: { [key in keyof APMPluginSetupDependencies]: { setup: Required[key]; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/constants.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/constants.ts index 7cb12fc5f2ed..37d0318325f9 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/constants.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/constants.ts @@ -57,6 +57,15 @@ export const NATIVE_CONNECTORS: NativeConnector[] = [ } ), }, + direct_connection: { + value: '', + label: i18n.translate( + 'xpack.enterpriseSearch.content.nativeConnectors.mongodb.configuration.directConnectionLabel', + { + defaultMessage: 'Use direct connection (true/false)', + } + ), + }, }, name: i18n.translate('xpack.enterpriseSearch.content.nativeConnectors.mongodb.name', { defaultMessage: 'MongoDB', diff --git a/x-pack/plugins/fleet/common/services/simplified_package_policy_helper.test.ts b/x-pack/plugins/fleet/common/services/simplified_package_policy_helper.test.ts index eec59a647c39..7d5a7966f91c 100644 --- a/x-pack/plugins/fleet/common/services/simplified_package_policy_helper.test.ts +++ b/x-pack/plugins/fleet/common/services/simplified_package_policy_helper.test.ts @@ -115,5 +115,36 @@ describe('toPackagePolicy', () => { 'nginx-nginx/metrics': ['nginx.stubstatus'], }); }); + + it('should to pass experimental_data_stream_features', () => { + const res = simplifiedPackagePolicytoNewPackagePolicy( + { + name: 'nginx-1', + namespace: 'default', + policy_id: 'policy123', + description: 'Test description', + }, + nginxPackageInfo as unknown as PackageInfo, + { + experimental_data_stream_features: [ + { + data_stream: 'logs-nginx.access', + features: { + synthetic_source: true, + }, + }, + ], + } + ); + + expect(res.package?.experimental_data_stream_features).toEqual([ + { + data_stream: 'logs-nginx.access', + features: { + synthetic_source: true, + }, + }, + ]); + }); }); }); diff --git a/x-pack/plugins/fleet/common/services/simplified_package_policy_helper.ts b/x-pack/plugins/fleet/common/services/simplified_package_policy_helper.ts index c0d05d1eeab2..aa6eb99bf360 100644 --- a/x-pack/plugins/fleet/common/services/simplified_package_policy_helper.ts +++ b/x-pack/plugins/fleet/common/services/simplified_package_policy_helper.ts @@ -11,6 +11,7 @@ import type { PackagePolicyConfigRecord, NewPackagePolicy, PackageInfo, + ExperimentalDataStreamFeature, } from '../types'; import { PackagePolicyValidationError } from '../errors'; @@ -66,7 +67,10 @@ type InputMap = Map { const keywordWithIndexFalseMapping = { properties: { keywordIndexFalse: { - ignore_above: 1024, type: 'keyword', doc_values: false, }, diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.ts index e0ea50b6420d..5605125bd4ff 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.ts @@ -24,7 +24,7 @@ import { import { getESAssetMetadata } from '../meta'; import { retryTransientEsErrors } from '../retry'; -import { getDefaultProperties, histogram, scaledFloat } from './mappings'; +import { getDefaultProperties, histogram, keyword, scaledFloat } from './mappings'; interface Properties { [key: string]: any; @@ -261,8 +261,7 @@ function _generateMappings( fieldProps = { ...fieldProps, ...generateDynamicAndEnabled(field), type: 'object' }; break; case 'keyword': - const keywordMapping = generateKeywordMapping(field); - fieldProps = { ...fieldProps, ...keywordMapping, type: 'keyword' }; + fieldProps = keyword(field); if (field.multi_fields) { fieldProps.fields = generateMultiFields(field.multi_fields); } @@ -359,7 +358,7 @@ function generateMultiFields(fields: Fields): MultiFields { multiFields[f.name] = { ...generateTextMapping(f), type: f.type }; break; case 'keyword': - multiFields[f.name] = { ...generateKeywordMapping(f), type: f.type }; + multiFields[f.name] = keyword(f); break; case 'long': case 'double': @@ -372,23 +371,6 @@ function generateMultiFields(fields: Fields): MultiFields { return multiFields; } -function generateKeywordMapping(field: Field): IndexTemplateMapping { - const mapping: IndexTemplateMapping = { - ignore_above: DEFAULT_IGNORE_ABOVE, - }; - if (field.ignore_above) { - mapping.ignore_above = field.ignore_above; - } - if (field.normalizer) { - mapping.normalizer = field.normalizer; - } - if (field.dimension) { - mapping.time_series_dimension = field.dimension; - delete mapping.ignore_above; - } - return mapping; -} - function generateTextMapping(field: Field): IndexTemplateMapping { const mapping: IndexTemplateMapping = {}; if (field.analyzer) { diff --git a/x-pack/plugins/fleet/server/services/package_policy.ts b/x-pack/plugins/fleet/server/services/package_policy.ts index 77c5b67d9a78..ba7e648f4c77 100644 --- a/x-pack/plugins/fleet/server/services/package_policy.ts +++ b/x-pack/plugins/fleet/server/services/package_policy.ts @@ -674,7 +674,9 @@ class PackagePolicyClientImpl implements PackagePolicyClient { } } - const deletePackagePolicy = async (id: string) => { + const idsToDelete: string[] = []; + + ids.forEach((id) => { try { const packagePolicy = packagePolicies.find((p) => p.id === id); @@ -694,10 +696,23 @@ class PackagePolicyClientImpl implements PackagePolicyClient { ); } - // TODO: replace this with savedObject BulkDelete when following PR is merged - // https://github.com/elastic/kibana/pull/139680 - await soClient.delete(SAVED_OBJECT_TYPE, id); + idsToDelete.push(id); + } catch (error) { + result.push({ + id, + success: false, + ...fleetErrorToResponseOptions(error), + }); + } + }); + const { statuses } = await soClient.bulkDelete( + idsToDelete.map((id) => ({ id, type: SAVED_OBJECT_TYPE })) + ); + + statuses.forEach(({ id, success, error }) => { + const packagePolicy = packagePolicies.find((p) => p.id === id); + if (success && packagePolicy) { result.push({ id, name: packagePolicy.name, @@ -709,16 +724,17 @@ class PackagePolicyClientImpl implements PackagePolicyClient { }, policy_id: packagePolicy.policy_id, }); - } catch (error) { + } else if (!success && error) { result.push({ id, success: false, - ...fleetErrorToResponseOptions(error), + statusCode: error.statusCode, + body: { + message: error.message, + }, }); } - }; - - await pMap(ids, deletePackagePolicy, { concurrency: 1000 }); + }); if (!options?.skipUnassignFromAgentPolicies) { const uniquePolicyIdsR = [ diff --git a/x-pack/plugins/fleet/server/types/models/package_policy.ts b/x-pack/plugins/fleet/server/types/models/package_policy.ts index 9763d09e2055..82ff572cd192 100644 --- a/x-pack/plugins/fleet/server/types/models/package_policy.ts +++ b/x-pack/plugins/fleet/server/types/models/package_policy.ts @@ -79,6 +79,15 @@ const PackagePolicyInputsSchema = { streams: schema.arrayOf(schema.object(PackagePolicyStreamsSchema)), }; +const ExperimentalDataStreamFeatures = schema.arrayOf( + schema.object({ + data_stream: schema.string(), + features: schema.object({ + synthetic_source: schema.boolean(), + }), + }) +); + const PackagePolicyBaseSchema = { name: schema.string(), description: schema.maybe(schema.string()), @@ -90,16 +99,7 @@ const PackagePolicyBaseSchema = { name: schema.string(), title: schema.string(), version: schema.string(), - experimental_data_stream_features: schema.maybe( - schema.arrayOf( - schema.object({ - data_stream: schema.string(), - features: schema.object({ - synthetic_source: schema.boolean(), - }), - }) - ) - ), + experimental_data_stream_features: schema.maybe(ExperimentalDataStreamFeatures), }) ), // Deprecated TODO create remove issue @@ -172,6 +172,7 @@ export const SimplifiedCreatePackagePolicyRequestBodySchema = schema.object({ package: schema.object({ name: schema.string(), version: schema.string(), + experimental_data_stream_features: schema.maybe(ExperimentalDataStreamFeatures), }), force: schema.maybe(schema.boolean()), vars: schema.maybe(SimplifiedVarsSchema), diff --git a/x-pack/plugins/graph/public/components/workspace_layout/workspace_layout.test.tsx b/x-pack/plugins/graph/public/components/workspace_layout/workspace_layout.test.tsx index 57b4ff613c88..cb43b5ec5d68 100644 --- a/x-pack/plugins/graph/public/components/workspace_layout/workspace_layout.test.tsx +++ b/x-pack/plugins/graph/public/components/workspace_layout/workspace_layout.test.tsx @@ -11,9 +11,15 @@ import { WorkspaceLayoutComponent } from '.'; import { coreMock } from '@kbn/core/public/mocks'; import { spacesPluginMock } from '@kbn/spaces-plugin/public/mocks'; import { NavigationPublicPluginStart as NavigationStart } from '@kbn/navigation-plugin/public'; -import { GraphSavePolicy, GraphWorkspaceSavedObject, IndexPatternProvider } from '../../types'; +import { + GraphSavePolicy, + GraphWorkspaceSavedObject, + IndexPatternProvider, + Workspace, +} from '../../types'; import { OverlayStart, Capabilities } from '@kbn/core/public'; import { SharingSavedObjectProps } from '../../helpers/use_workspace_loader'; +import { GraphVisualization } from '../graph_visualization'; jest.mock('react-router-dom', () => { const useLocation = () => ({ @@ -45,6 +51,7 @@ describe('workspace_layout', () => { aliasTargetId: '', } as SharingSavedObjectProps, spaces: spacesPluginMock.createStartContract(), + workspace: {} as unknown as Workspace, }; it('should display conflict notification if outcome is conflict', () => { shallow( @@ -60,4 +67,44 @@ describe('workspace_layout', () => { otherObjectPath: '#/workspace/conflictId?query={}', }); }); + it('should not show GraphVisualization when workspaceInitialized is false, savedWorkspace.id is undefined, and savedWorkspace.isSaving is false', () => { + const component = shallow( + + ); + expect(component.find(GraphVisualization).exists()).toBe(false); + }); + it('should show GraphVisualization when workspaceInitialized is true', () => { + const component = shallow( + + ); + expect(component.find(GraphVisualization).exists()).toBe(true); + }); + it('should show GraphVisualization when savedWorkspace.id is defined', () => { + const component = shallow( + + ); + expect(component.find(GraphVisualization).exists()).toBe(true); + }); + it('should show GraphVisualization when savedWorkspace.isSaving is true', () => { + const component = shallow( + + ); + expect(component.find(GraphVisualization).exists()).toBe(true); + }); }); diff --git a/x-pack/plugins/graph/public/components/workspace_layout/workspace_layout.tsx b/x-pack/plugins/graph/public/components/workspace_layout/workspace_layout.tsx index 6cfbbcf7d910..3eede479bd80 100644 --- a/x-pack/plugins/graph/public/components/workspace_layout/workspace_layout.tsx +++ b/x-pack/plugins/graph/public/components/workspace_layout/workspace_layout.tsx @@ -91,7 +91,11 @@ export const WorkspaceLayoutComponent = ({ const search = useLocation().search; const urlQuery = new URLSearchParams(search).get('query'); - const isInitialized = Boolean(workspaceInitialized || savedWorkspace.id); + // savedWorkspace.id gets set to null while saving a copy of an existing + // workspace, so we need to check for savedWorkspace.isSaving as well + const isInitialized = Boolean( + workspaceInitialized || savedWorkspace.id || savedWorkspace.isSaving + ); const selectSelected = useCallback((node: WorkspaceNode) => { selectedNode.current = node; diff --git a/x-pack/plugins/graph/public/helpers/saved_workspace_utils.test.ts b/x-pack/plugins/graph/public/helpers/saved_workspace_utils.test.ts new file mode 100644 index 000000000000..7d4027c91858 --- /dev/null +++ b/x-pack/plugins/graph/public/helpers/saved_workspace_utils.test.ts @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { coreMock } from '@kbn/core/public/mocks'; +import { GraphWorkspaceSavedObject } from '../types'; +import { saveSavedWorkspace } from './saved_workspace_utils'; + +const core = coreMock.createStart(); + +describe('saved_workspace_utils', () => { + describe('saveSavedWorkspace', () => { + it('should delete the savedWorkspace id and set isSaving to true immediately when copyOnSave is true', async () => { + const savedWorkspace = { + id: '123', + title: 'my workspace', + lastSavedTitle: 'my workspace', + migrationVersion: {}, + wsState: '{ "indexPattern": "my-index-pattern" }', + getEsType: () => 'graph-workspace', + copyOnSave: true, + isSaving: false, + } as GraphWorkspaceSavedObject; + const promise = saveSavedWorkspace( + savedWorkspace, + {}, + { + savedObjectsClient: { + ...core.savedObjects.client, + find: jest.fn().mockResolvedValue({ savedObjects: [] }), + create: jest.fn().mockResolvedValue({ id: '456' }), + }, + overlays: core.overlays, + } + ); + expect(savedWorkspace.id).toBe(undefined); + expect(savedWorkspace.isSaving).toBe(true); + const id = await promise; + expect(id).toBe('456'); + expect(savedWorkspace.id).toBe('456'); + expect(savedWorkspace.isSaving).toBe(false); + }); + }); +}); diff --git a/x-pack/plugins/graph/public/helpers/saved_workspace_utils.ts b/x-pack/plugins/graph/public/helpers/saved_workspace_utils.ts index 202d13f9cd53..03e377792f1a 100644 --- a/x-pack/plugins/graph/public/helpers/saved_workspace_utils.ts +++ b/x-pack/plugins/graph/public/helpers/saved_workspace_utils.ts @@ -162,18 +162,6 @@ export async function saveSavedWorkspace( overlays: OverlayStart; } ) { - // Save the original id in case the save fails. - const originalId = savedObject.id; - // Read https://github.com/elastic/kibana/issues/9056 and - // https://github.com/elastic/kibana/issues/9012 for some background into why this copyOnSave variable - // exists. - // The goal is to move towards a better rename flow, but since our users have been conditioned - // to expect a 'save as' flow during a rename, we are keeping the logic the same until a better - // UI/UX can be worked out. - if (savedObject.copyOnSave) { - delete savedObject.id; - } - let attributes: SavedObjectAttributes = {}; forOwn(mapping, (fieldType, fieldName) => { @@ -191,14 +179,28 @@ export async function saveSavedWorkspace( throw new Error('References not returned from extractReferences'); } + // Save the original id in case the save fails. + const originalId = savedObject.id; + try { + // Read https://github.com/elastic/kibana/issues/9056 and + // https://github.com/elastic/kibana/issues/9012 for some background into why this copyOnSave variable + // exists. + // The goal is to move towards a better rename flow, but since our users have been conditioned + // to expect a 'save as' flow during a rename, we are keeping the logic the same until a better + // UI/UX can be worked out. + if (savedObject.copyOnSave) { + delete savedObject.id; + } + + savedObject.isSaving = true; + await checkForDuplicateTitle( savedObject as any, isTitleDuplicateConfirmed, onTitleDuplicate, services ); - savedObject.isSaving = true; const createOpt = { id: savedObject.id, diff --git a/x-pack/plugins/lens/public/app_plugin/settings_menu.tsx b/x-pack/plugins/lens/public/app_plugin/settings_menu.tsx index e4cd670286ef..f4447929d806 100644 --- a/x-pack/plugins/lens/public/app_plugin/settings_menu.tsx +++ b/x-pack/plugins/lens/public/app_plugin/settings_menu.tsx @@ -45,13 +45,14 @@ export function SettingsMenu({ const dispatch = useLensDispatch(); const toggleAutoApply = useCallback(() => { + onClose(); writeToStorage( new Storage(localStorage), AUTO_APPLY_DISABLED_STORAGE_KEY, String(autoApplyEnabled) ); dispatch(autoApplyEnabled ? disableAutoApply() : enableAutoApply()); - }, [dispatch, autoApplyEnabled]); + }, [dispatch, autoApplyEnabled, onClose]); return ( - + {item.nested === 'child' && } { - let esClientMock: jest.Mocked; + let esClientMock: ElasticsearchClientMock; let loggerMock: jest.Mocked; beforeEach(() => { @@ -158,6 +162,26 @@ describe('TransformManager', () => { expect(esClientMock.transform.deleteTransform).toHaveBeenCalledTimes(1); }); + + it('retries on transient error', async () => { + esClientMock.transform.deleteTransform.mockRejectedValueOnce( + new EsErrors.ConnectionError('irrelevant') + ); + // @ts-ignore defining only a subset of the possible SLI + const generators: Record = { + 'slo.apm.transaction_error_rate': new ApmTransactionErrorRateTransformGenerator(), + }; + const transformManager = new DefaultTransformManager( + generators, + esClientMock, + loggerMock, + SPACE_ID + ); + + await transformManager.uninstall('slo-transform-id'); + + expect(esClientMock.transform.deleteTransform).toHaveBeenCalledTimes(2); + }); }); }); diff --git a/x-pack/plugins/observability/server/services/slo/transform_manager.ts b/x-pack/plugins/observability/server/services/slo/transform_manager.ts index 178d6dacaa43..ab7799a4a00c 100644 --- a/x-pack/plugins/observability/server/services/slo/transform_manager.ts +++ b/x-pack/plugins/observability/server/services/slo/transform_manager.ts @@ -8,6 +8,7 @@ import { ElasticsearchClient, Logger } from '@kbn/core/server'; import { SLO, SLITypes } from '../../types/models'; +import { retryTransientEsErrors } from '../../utils/retry'; import { TransformGenerator } from './transform_generators'; type TransformId = string; @@ -36,7 +37,9 @@ export class DefaultTransformManager implements TransformManager { const transformParams = generator.getTransformParams(slo, this.spaceId); try { - await this.esClient.transform.putTransform(transformParams); + await retryTransientEsErrors(() => this.esClient.transform.putTransform(transformParams), { + logger: this.logger, + }); } catch (err) { this.logger.error(`Cannot create transform for ${slo.indicator.type} SLO type: ${err}`); throw err; @@ -47,9 +50,10 @@ export class DefaultTransformManager implements TransformManager { async start(transformId: TransformId): Promise { try { - await this.esClient.transform.startTransform( - { transform_id: transformId }, - { ignore: [409] } + await retryTransientEsErrors( + () => + this.esClient.transform.startTransform({ transform_id: transformId }, { ignore: [409] }), + { logger: this.logger } ); } catch (err) { this.logger.error(`Cannot start transform id ${transformId}: ${err}`); @@ -59,9 +63,13 @@ export class DefaultTransformManager implements TransformManager { async stop(transformId: TransformId): Promise { try { - await this.esClient.transform.stopTransform( - { transform_id: transformId, wait_for_completion: true }, - { ignore: [404] } + await retryTransientEsErrors( + () => + this.esClient.transform.stopTransform( + { transform_id: transformId, wait_for_completion: true }, + { ignore: [404] } + ), + { logger: this.logger } ); } catch (err) { this.logger.error(`Cannot stop transform id ${transformId}: ${err}`); @@ -71,9 +79,13 @@ export class DefaultTransformManager implements TransformManager { async uninstall(transformId: TransformId): Promise { try { - await this.esClient.transform.deleteTransform( - { transform_id: transformId, force: true }, - { ignore: [404] } + await retryTransientEsErrors( + () => + this.esClient.transform.deleteTransform( + { transform_id: transformId, force: true }, + { ignore: [404] } + ), + { logger: this.logger } ); } catch (err) { this.logger.error(`Cannot delete transform id ${transformId}: ${err}`); diff --git a/x-pack/plugins/observability/server/utils/retry.test.ts b/x-pack/plugins/observability/server/utils/retry.test.ts new file mode 100644 index 000000000000..25870fd24aa7 --- /dev/null +++ b/x-pack/plugins/observability/server/utils/retry.test.ts @@ -0,0 +1,87 @@ +/* + * 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. + */ + +jest.mock('timers/promises'); +import { setTimeout } from 'timers/promises'; +import { loggerMock } from '@kbn/logging-mocks'; +import { errors as EsErrors } from '@elastic/elasticsearch'; + +import { retryTransientEsErrors } from './retry'; + +const setTimeoutMock = setTimeout as jest.Mock< + ReturnType, + Parameters +>; + +describe('retryTransientErrors', () => { + beforeEach(() => { + setTimeoutMock.mockClear(); + }); + + it("doesn't retry if operation is successful", async () => { + const esCallMock = jest.fn().mockResolvedValue('success'); + expect(await retryTransientEsErrors(esCallMock)).toEqual('success'); + expect(esCallMock).toHaveBeenCalledTimes(1); + }); + + it('logs an warning message on retry', async () => { + const logger = loggerMock.create(); + const esCallMock = jest + .fn() + .mockRejectedValueOnce(new EsErrors.ConnectionError('foo')) + .mockResolvedValue('success'); + + await retryTransientEsErrors(esCallMock, { logger }); + expect(logger.warn).toHaveBeenCalledTimes(1); + expect(logger.warn.mock.calls[0][0]).toMatch( + `Retrying Elasticsearch operation after [2s] due to error: ConnectionError: foo ConnectionError: foo` + ); + }); + + it('retries with an exponential backoff', async () => { + let attempt = 0; + const esCallMock = jest.fn(async () => { + attempt++; + if (attempt < 5) { + throw new EsErrors.ConnectionError('foo'); + } else { + return 'success'; + } + }); + + expect(await retryTransientEsErrors(esCallMock)).toEqual('success'); + expect(setTimeoutMock.mock.calls).toEqual([[2000], [4000], [8000], [16000]]); + expect(esCallMock).toHaveBeenCalledTimes(5); + }); + + it('retries each supported error type', async () => { + const errors = [ + new EsErrors.NoLivingConnectionsError('no living connection', { + warnings: [], + meta: {} as any, + }), + new EsErrors.ConnectionError('no connection'), + new EsErrors.TimeoutError('timeout'), + new EsErrors.ResponseError({ statusCode: 503, meta: {} as any, warnings: [] }), + new EsErrors.ResponseError({ statusCode: 408, meta: {} as any, warnings: [] }), + new EsErrors.ResponseError({ statusCode: 410, meta: {} as any, warnings: [] }), + ]; + + for (const error of errors) { + const esCallMock = jest.fn().mockRejectedValueOnce(error).mockResolvedValue('success'); + expect(await retryTransientEsErrors(esCallMock)).toEqual('success'); + expect(esCallMock).toHaveBeenCalledTimes(2); + } + }); + + it('does not retry unsupported errors', async () => { + const error = new Error('foo!'); + const esCallMock = jest.fn().mockRejectedValueOnce(error).mockResolvedValue('success'); + await expect(retryTransientEsErrors(esCallMock)).rejects.toThrow(error); + expect(esCallMock).toHaveBeenCalledTimes(1); + }); +}); diff --git a/x-pack/plugins/observability/server/utils/retry.ts b/x-pack/plugins/observability/server/utils/retry.ts new file mode 100644 index 000000000000..421289d1c047 --- /dev/null +++ b/x-pack/plugins/observability/server/utils/retry.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 { setTimeout } from 'timers/promises'; +import { errors as EsErrors } from '@elastic/elasticsearch'; +import type { Logger } from '@kbn/logging'; + +const MAX_ATTEMPTS = 5; + +const retryResponseStatuses = [ + 503, // ServiceUnavailable + 408, // RequestTimeout + 410, // Gone +]; + +const isRetryableError = (e: any) => + e instanceof EsErrors.NoLivingConnectionsError || + e instanceof EsErrors.ConnectionError || + e instanceof EsErrors.TimeoutError || + (e instanceof EsErrors.ResponseError && retryResponseStatuses.includes(e?.statusCode!)); + +/** + * Retries any transient network or configuration issues encountered from Elasticsearch with an exponential backoff. + * Should only be used to wrap operations that are idempotent and can be safely executed more than once. + */ +export const retryTransientEsErrors = async ( + esCall: () => Promise, + { logger, attempt = 0 }: { logger?: Logger; attempt?: number } = {} +): Promise => { + try { + return await esCall(); + } catch (e) { + if (attempt < MAX_ATTEMPTS && isRetryableError(e)) { + const retryCount = attempt + 1; + const retryDelaySec = Math.min(Math.pow(2, retryCount), 64); // 2s, 4s, 8s, 16s, 32s, 64s, 64s, 64s ... + + logger?.warn( + `Retrying Elasticsearch operation after [${retryDelaySec}s] due to error: ${e.toString()} ${ + e.stack + }` + ); + + await setTimeout(retryDelaySec * 1000); + return retryTransientEsErrors(esCall, { logger, attempt: retryCount }); + } + + throw e; + } +}; diff --git a/x-pack/plugins/security_solution/public/common/components/top_n/index.tsx b/x-pack/plugins/security_solution/public/common/components/top_n/index.tsx index 8771d75ccf81..589065e0812b 100644 --- a/x-pack/plugins/security_solution/public/common/components/top_n/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/top_n/index.tsx @@ -15,12 +15,12 @@ import { InputsModelId } from '../../store/inputs/constants'; import { useGlobalTime } from '../../containers/use_global_time'; import type { BrowserFields } from '../../containers/source'; import { useKibana } from '../../lib/kibana'; +import { combineQueries } from '../../lib/kuery'; import type { inputsModel, State } from '../../store'; import { inputsSelectors } from '../../store'; import { timelineDefaults } from '../../../timelines/store/timeline/defaults'; import { timelineSelectors } from '../../../timelines/store/timeline'; import type { TimelineModel } from '../../../timelines/store/timeline/model'; -import { combineQueries } from '../../../timelines/components/timeline/helpers'; import { getOptions } from './helpers'; import { TopN } from './top_n'; diff --git a/x-pack/plugins/security_solution/public/common/lib/keury/index.ts b/x-pack/plugins/security_solution/public/common/lib/kuery/index.ts similarity index 95% rename from x-pack/plugins/security_solution/public/common/lib/keury/index.ts rename to x-pack/plugins/security_solution/public/common/lib/kuery/index.ts index 735996d762ff..55be876f3867 100644 --- a/x-pack/plugins/security_solution/public/common/lib/keury/index.ts +++ b/x-pack/plugins/security_solution/public/common/lib/kuery/index.ts @@ -11,4 +11,5 @@ export { convertToBuildEsQuery, escapeKuery, escapeQueryValue, + combineQueries, } from '@kbn/timelines-plugin/public'; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx index 0850969019bb..6f2a0dfe0752 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx @@ -57,7 +57,7 @@ import { omitTypenameInTimeline, formatTimelineResultToModel, } from '../../../timelines/components/open_timeline/helpers'; -import { convertKueryToElasticSearchQuery } from '../../../common/lib/keury'; +import { convertKueryToElasticSearchQuery } from '../../../common/lib/kuery'; import { getField, getFieldKey } from '../../../helpers'; import { replaceTemplateFieldFromQuery, diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx index bf183ca40572..49927599cb1e 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx @@ -25,7 +25,7 @@ import { SourcererScopeName } from '../../../common/store/sourcerer/model'; import { DEFAULT_COLUMN_MIN_WIDTH } from '../../../timelines/components/timeline/body/constants'; import { getDefaultControlColumn } from '../../../timelines/components/timeline/body/control_columns'; import { defaultRowRenderers } from '../../../timelines/components/timeline/body/renderers'; -import { combineQueries } from '../../../timelines/components/timeline/helpers'; +import { combineQueries } from '../../../common/lib/kuery'; import { timelineActions, timelineSelectors } from '../../../timelines/store/timeline'; import { timelineDefaults } from '../../../timelines/store/timeline/defaults'; import type { TimelineModel } from '../../../timelines/store/timeline/model'; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/query_bar/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/query_bar/index.tsx index 6d96695d3c79..a5316eabb11f 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/query_bar/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/query_bar/index.tsx @@ -19,7 +19,7 @@ import type { ActionTimelineToShow } from '../../../../timelines/components/open import { QueryBar } from '../../../../common/components/query_bar'; import { buildGlobalQuery } from '../../../../timelines/components/timeline/helpers'; import { getDataProviderFilter } from '../../../../timelines/components/timeline/query_bar'; -import { convertKueryToElasticSearchQuery } from '../../../../common/lib/keury'; +import { convertKueryToElasticSearchQuery } from '../../../../common/lib/kuery'; import { useKibana } from '../../../../common/lib/kibana'; import type { TimelineModel } from '../../../../timelines/store/timeline/model'; import { useSavedQueryServices } from '../../../../common/utils/saved_query_services'; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/use_preview_histogram.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/use_preview_histogram.tsx index a0c6e706c19e..d3814fe1e8c1 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/use_preview_histogram.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/use_preview_histogram.tsx @@ -10,7 +10,7 @@ import { getEsQueryConfig } from '@kbn/data-plugin/common'; import type { DataViewBase } from '@kbn/es-query'; import { useMatrixHistogramCombined } from '../../../../common/containers/matrix_histogram'; import { MatrixHistogramType } from '../../../../../common/search_strategy'; -import { convertToBuildEsQuery } from '../../../../common/lib/keury'; +import { convertToBuildEsQuery } from '../../../../common/lib/kuery'; import { useKibana } from '../../../../common/lib/kibana'; import { QUERY_PREVIEW_ERROR } from './translations'; import { DEFAULT_PREVIEW_INDEX } from '../../../../../common/constants'; diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/utils.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/utils.ts index 083b75a33c70..b4de6a95daaf 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/utils.ts +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/utils.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { escapeKuery } from '../../../../common/lib/keury'; +import { escapeKuery } from '../../../../common/lib/kuery'; import type { FilterOptions } from './types'; const SEARCHABLE_RULE_PARAMS = [ diff --git a/x-pack/plugins/security_solution/public/hosts/pages/details/helpers.ts b/x-pack/plugins/security_solution/public/hosts/pages/details/helpers.ts index 0a09c7ec6dad..99cdba73760a 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/details/helpers.ts +++ b/x-pack/plugins/security_solution/public/hosts/pages/details/helpers.ts @@ -6,7 +6,7 @@ */ import type { Filter } from '@kbn/es-query'; -import { escapeQueryValue } from '../../../common/lib/keury'; +import { escapeQueryValue } from '../../../common/lib/kuery'; /** Returns the kqlQueryExpression for the `Events` widget on the `Host Details` page */ export const getHostDetailsEventsKqlQueryExpression = ({ diff --git a/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx b/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx index fb39b402c84d..b801dd825a7a 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx @@ -40,7 +40,7 @@ import { SiemSearchBar } from '../../../common/components/search_bar'; import { SecuritySolutionPageWrapper } from '../../../common/components/page_wrapper'; import { useGlobalTime } from '../../../common/containers/use_global_time'; import { useKibana } from '../../../common/lib/kibana'; -import { convertToBuildEsQuery } from '../../../common/lib/keury'; +import { convertToBuildEsQuery } from '../../../common/lib/kuery'; import { inputsSelectors } from '../../../common/store'; import { setHostDetailsTablesActivePageToZero } from '../../store/actions'; import { setAbsoluteRangeDatePicker } from '../../../common/store/inputs/actions'; diff --git a/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx b/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx index 3195137ca389..b9cefa660a45 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx @@ -30,7 +30,7 @@ import { useGlobalTime } from '../../common/containers/use_global_time'; import { TimelineId } from '../../../common/types/timeline'; import { LastEventIndexKey, RiskScoreEntity } from '../../../common/search_strategy'; import { useKibana } from '../../common/lib/kibana'; -import { convertToBuildEsQuery } from '../../common/lib/keury'; +import { convertToBuildEsQuery } from '../../common/lib/kuery'; import type { State } from '../../common/store'; import { inputsSelectors } from '../../common/store'; import { setAbsoluteRangeDatePicker } from '../../common/store/inputs/actions'; diff --git a/x-pack/plugins/security_solution/public/kubernetes/pages/index.tsx b/x-pack/plugins/security_solution/public/kubernetes/pages/index.tsx index 58e0ae3914ad..d938e7a97278 100644 --- a/x-pack/plugins/security_solution/public/kubernetes/pages/index.tsx +++ b/x-pack/plugins/security_solution/public/kubernetes/pages/index.tsx @@ -20,7 +20,7 @@ import { useGlobalFullScreen } from '../../common/containers/use_full_screen'; import { useSourcererDataView } from '../../common/containers/sourcerer'; import { useGlobalTime } from '../../common/containers/use_global_time'; import { useDeepEqualSelector } from '../../common/hooks/use_selector'; -import { convertToBuildEsQuery } from '../../common/lib/keury'; +import { convertToBuildEsQuery } from '../../common/lib/kuery'; import { useInvalidFilterQuery } from '../../common/hooks/use_invalid_filter_query'; import { SessionsView } from '../../common/components/sessions_viewer'; import { TimelineId } from '../../../common/types/timeline'; diff --git a/x-pack/plugins/security_solution/public/management/hooks/endpoint/use_get_endpoint_action_list.test.ts b/x-pack/plugins/security_solution/public/management/hooks/endpoint/use_get_endpoint_action_list.test.ts index 328a465603e6..0afd8788ad33 100644 --- a/x-pack/plugins/security_solution/public/management/hooks/endpoint/use_get_endpoint_action_list.test.ts +++ b/x-pack/plugins/security_solution/public/management/hooks/endpoint/use_get_endpoint_action_list.test.ts @@ -64,7 +64,7 @@ describe('useGetEndpointActionList hook', () => { page: 2, pageSize: 20, startDate: 'now-5d', - userIds: ['elastic', 'citsale'], + userIds: ['*elastic*', '*citsale*'], }, }); }); diff --git a/x-pack/plugins/security_solution/public/management/hooks/endpoint/use_get_endpoint_action_list.ts b/x-pack/plugins/security_solution/public/management/hooks/endpoint/use_get_endpoint_action_list.ts index 957afe5e0c15..eba9b162d67f 100644 --- a/x-pack/plugins/security_solution/public/management/hooks/endpoint/use_get_endpoint_action_list.ts +++ b/x-pack/plugins/security_solution/public/management/hooks/endpoint/use_get_endpoint_action_list.ts @@ -24,6 +24,14 @@ export const useGetEndpointActionList = ( ): UseQueryResult> => { const http = useHttp(); + // prepend and append * to userIds for fuzzy search + let userIds = query.userIds; + if (typeof query.userIds === 'string') { + userIds = `*${query.userIds}*`; + } else if (Array.isArray(query.userIds)) { + userIds = query.userIds.map((userId) => `*${userId}*`); + } + return useQuery>({ queryKey: ['get-action-list', query], ...options, @@ -37,7 +45,7 @@ export const useGetEndpointActionList = ( pageSize: query.pageSize, startDate: query.startDate, statuses: query.statuses, - userIds: query.userIds, + userIds, }, }); }, diff --git a/x-pack/plugins/security_solution/public/network/pages/details/index.tsx b/x-pack/plugins/security_solution/public/network/pages/details/index.tsx index c7c435b608d7..964e660fed52 100644 --- a/x-pack/plugins/security_solution/public/network/pages/details/index.tsx +++ b/x-pack/plugins/security_solution/public/network/pages/details/index.tsx @@ -33,7 +33,7 @@ import { SecuritySolutionPageWrapper } from '../../../common/components/page_wra import { useNetworkDetails, ID } from '../../containers/details'; import { useKibana } from '../../../common/lib/kibana'; import { decodeIpv6 } from '../../../common/lib/helpers'; -import { convertToBuildEsQuery } from '../../../common/lib/keury'; +import { convertToBuildEsQuery } from '../../../common/lib/kuery'; import { inputsSelectors } from '../../../common/store'; import { setAbsoluteRangeDatePicker } from '../../../common/store/inputs/actions'; import { setNetworkDetailsTablesActivePageToZero } from '../../store/actions'; diff --git a/x-pack/plugins/security_solution/public/network/pages/network.tsx b/x-pack/plugins/security_solution/public/network/pages/network.tsx index a28f25119511..92bc47ba04c7 100644 --- a/x-pack/plugins/security_solution/public/network/pages/network.tsx +++ b/x-pack/plugins/security_solution/public/network/pages/network.tsx @@ -30,7 +30,7 @@ import { useGlobalFullScreen } from '../../common/containers/use_full_screen'; import { useGlobalTime } from '../../common/containers/use_global_time'; import { LastEventIndexKey } from '../../../common/search_strategy'; import { useKibana } from '../../common/lib/kibana'; -import { convertToBuildEsQuery } from '../../common/lib/keury'; +import { convertToBuildEsQuery } from '../../common/lib/kuery'; import { inputsSelectors } from '../../common/store'; import { setAbsoluteRangeDatePicker } from '../../common/store/inputs/actions'; import { SpyRoute } from '../../common/utils/route/spy_routes'; diff --git a/x-pack/plugins/security_solution/public/overview/components/event_counts/index.tsx b/x-pack/plugins/security_solution/public/overview/components/event_counts/index.tsx index 9f73cbf9a2d4..5194d697c96b 100644 --- a/x-pack/plugins/security_solution/public/overview/components/event_counts/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/event_counts/index.tsx @@ -14,7 +14,7 @@ import { ID as OverviewHostQueryId } from '../../containers/overview_host'; import { OverviewHost } from '../overview_host'; import { OverviewNetwork } from '../overview_network'; import { useKibana } from '../../../common/lib/kibana'; -import { convertToBuildEsQuery } from '../../../common/lib/keury'; +import { convertToBuildEsQuery } from '../../../common/lib/kuery'; import type { GlobalTimeArgs } from '../../../common/containers/use_global_time'; import { useInvalidFilterQuery } from '../../../common/hooks/use_invalid_filter_query'; import { diff --git a/x-pack/plugins/security_solution/public/overview/components/events_by_dataset/index.tsx b/x-pack/plugins/security_solution/public/overview/components/events_by_dataset/index.tsx index 15768c635981..4e35cae87334 100644 --- a/x-pack/plugins/security_solution/public/overview/components/events_by_dataset/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/events_by_dataset/index.tsx @@ -22,7 +22,7 @@ import type { MatrixHistogramConfigs, MatrixHistogramOption, } from '../../../common/components/matrix_histogram/types'; -import { convertToBuildEsQuery } from '../../../common/lib/keury'; +import { convertToBuildEsQuery } from '../../../common/lib/kuery'; import { useKibana, useUiSetting$ } from '../../../common/lib/kibana'; import { eventsStackByOptions, diff --git a/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.tsx index f7e267de67d0..367a145774d7 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.tsx @@ -49,7 +49,8 @@ import { startSelector, endSelector, } from '../../../../common/components/super_date_picker/selectors'; -import { combineQueries, focusActiveTimelineButton } from '../../timeline/helpers'; +import { focusActiveTimelineButton } from '../../timeline/helpers'; +import { combineQueries } from '../../../../common/lib/kuery'; import { SourcererScopeName } from '../../../../common/store/sourcerer/model'; import { ActiveTimelines } from './active_timelines'; import * as i18n from './translations'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/network_details/expandable_network.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/network_details/expandable_network.tsx index 05421e9891e5..8841a53f43d6 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/network_details/expandable_network.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/network_details/expandable_network.tsx @@ -21,7 +21,7 @@ import { useGlobalTime } from '../../../../common/containers/use_global_time'; import { networkToCriteria } from '../../../../common/components/ml/criteria/network_to_criteria'; import { scoreIntervalToDateTime } from '../../../../common/components/ml/score/score_interval_to_datetime'; import { useKibana } from '../../../../common/lib/kibana'; -import { convertToBuildEsQuery } from '../../../../common/lib/keury'; +import { convertToBuildEsQuery } from '../../../../common/lib/kuery'; import { inputsSelectors } from '../../../../common/store'; import { setAbsoluteRangeDatePicker } from '../../../../common/store/inputs/actions'; import { useSourcererDataView } from '../../../../common/containers/sourcerer'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.test.tsx index 5f6713c82c42..4e391a95fc05 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.test.tsx @@ -6,14 +6,11 @@ */ import { cloneDeep } from 'lodash/fp'; -import { mockIndexPattern } from '../../../common/mock'; import { DataProviderType } from './data_providers/data_provider'; import { mockDataProviders } from './data_providers/mock/mock_data_providers'; -import { buildGlobalQuery, combineQueries, resolverIsShowing, showGlobalFilters } from './helpers'; +import { buildGlobalQuery } from './helpers'; import { mockBrowserFields } from '../../../common/containers/source/mock'; -import type { EsQueryConfig, Filter } from '@kbn/es-query'; -import { FilterStateStore } from '@kbn/es-query'; const cleanUpKqlQuery = (str: string) => str.replace(/\n/g, '').replace(/\s\s+/g, ' '); @@ -220,388 +217,3 @@ describe('Build KQL Query', () => { ); }); }); - -describe('Combined Queries', () => { - const config: EsQueryConfig = { - allowLeadingWildcards: true, - queryStringOptions: {}, - ignoreFilterIfFieldNotInIndex: true, - dateFormatTZ: 'America/New_York', - }; - test('No Data Provider & No kqlQuery & and isEventViewer is false', () => { - expect( - combineQueries({ - config, - dataProviders: [], - indexPattern: mockIndexPattern, - browserFields: mockBrowserFields, - filters: [], - kqlQuery: { query: '', language: 'kuery' }, - kqlMode: 'search', - }) - ).toBeNull(); - }); - - test('No Data Provider & No kqlQuery & isEventViewer is true', () => { - const isEventViewer = true; - expect( - combineQueries({ - config, - dataProviders: [], - indexPattern: mockIndexPattern, - browserFields: mockBrowserFields, - filters: [], - kqlQuery: { query: '', language: 'kuery' }, - kqlMode: 'search', - isEventViewer, - }) - ).toEqual({ - filterQuery: '{"bool":{"must":[],"filter":[],"should":[],"must_not":[]}}', - }); - }); - - test('No Data Provider & No kqlQuery & with Filters', () => { - const isEventViewer = true; - expect( - combineQueries({ - config, - dataProviders: [], - indexPattern: mockIndexPattern, - browserFields: mockBrowserFields, - filters: [ - { - $state: { store: FilterStateStore.APP_STATE }, - meta: { - alias: null, - disabled: false, - key: 'event.category', - negate: false, - params: { query: 'file' }, - type: 'phrase', - }, - query: { match_phrase: { 'event.category': 'file' } }, - }, - { - $state: { store: FilterStateStore.APP_STATE }, - meta: { - alias: null, - disabled: false, - key: 'host.name', - negate: false, - type: 'exists', - value: 'exists', - }, - query: { exists: { field: 'host.name' } }, - } as Filter, - ], - kqlQuery: { query: '', language: 'kuery' }, - kqlMode: 'search', - isEventViewer, - }) - ).toEqual({ - filterQuery: - '{"bool":{"must":[],"filter":[{"exists":{"field":"host.name"}}],"should":[],"must_not":[]}}', - }); - }); - - test('Only Data Provider', () => { - const dataProviders = cloneDeep(mockDataProviders.slice(0, 1)); - const { filterQuery, kqlError } = combineQueries({ - config, - dataProviders, - indexPattern: mockIndexPattern, - browserFields: mockBrowserFields, - filters: [], - kqlQuery: { query: '', language: 'kuery' }, - kqlMode: 'search', - })!; - expect(filterQuery).toEqual( - '{"bool":{"must":[],"filter":[{"bool":{"should":[{"match_phrase":{"name":"Provider 1"}}],"minimum_should_match":1}}],"should":[],"must_not":[]}}' - ); - expect(kqlError).toBeUndefined(); - }); - - test('Only Data Provider with timestamp (string input)', () => { - const dataProviders = cloneDeep(mockDataProviders.slice(0, 1)); - dataProviders[0].queryMatch.field = '@timestamp'; - dataProviders[0].queryMatch.value = '2018-03-23T23:36:23.232Z'; - const { filterQuery, kqlError } = combineQueries({ - config, - dataProviders, - indexPattern: mockIndexPattern, - browserFields: mockBrowserFields, - filters: [], - kqlQuery: { query: '', language: 'kuery' }, - kqlMode: 'search', - })!; - expect(filterQuery).toMatchInlineSnapshot( - `"{\\"bool\\":{\\"must\\":[],\\"filter\\":[{\\"bool\\":{\\"should\\":[{\\"range\\":{\\"@timestamp\\":{\\"gte\\":\\"1521848183232\\",\\"lte\\":\\"1521848183232\\"}}}],\\"minimum_should_match\\":1}}],\\"should\\":[],\\"must_not\\":[]}}"` - ); - expect(kqlError).toBeUndefined(); - }); - - test('Only Data Provider with timestamp (numeric input)', () => { - const dataProviders = cloneDeep(mockDataProviders.slice(0, 1)); - dataProviders[0].queryMatch.field = '@timestamp'; - dataProviders[0].queryMatch.value = 1521848183232; - const { filterQuery, kqlError } = combineQueries({ - config, - dataProviders, - indexPattern: mockIndexPattern, - browserFields: mockBrowserFields, - filters: [], - kqlQuery: { query: '', language: 'kuery' }, - kqlMode: 'search', - })!; - expect(filterQuery).toMatchInlineSnapshot( - `"{\\"bool\\":{\\"must\\":[],\\"filter\\":[{\\"bool\\":{\\"should\\":[{\\"range\\":{\\"@timestamp\\":{\\"gte\\":\\"1521848183232\\",\\"lte\\":\\"1521848183232\\"}}}],\\"minimum_should_match\\":1}}],\\"should\\":[],\\"must_not\\":[]}}"` - ); - expect(kqlError).toBeUndefined(); - }); - - test('Only Data Provider with a date type (string input)', () => { - const dataProviders = cloneDeep(mockDataProviders.slice(0, 1)); - dataProviders[0].queryMatch.field = 'event.end'; - dataProviders[0].queryMatch.value = '2018-03-23T23:36:23.232Z'; - const { filterQuery, kqlError } = combineQueries({ - config, - dataProviders, - indexPattern: mockIndexPattern, - browserFields: mockBrowserFields, - filters: [], - kqlQuery: { query: '', language: 'kuery' }, - kqlMode: 'search', - })!; - expect(filterQuery).toMatchInlineSnapshot( - `"{\\"bool\\":{\\"must\\":[],\\"filter\\":[{\\"bool\\":{\\"should\\":[{\\"match\\":{\\"event.end\\":\\"1521848183232\\"}}],\\"minimum_should_match\\":1}}],\\"should\\":[],\\"must_not\\":[]}}"` - ); - expect(kqlError).toBeUndefined(); - }); - - test('Only Data Provider with date type (numeric input)', () => { - const dataProviders = cloneDeep(mockDataProviders.slice(0, 1)); - dataProviders[0].queryMatch.field = 'event.end'; - dataProviders[0].queryMatch.value = 1521848183232; - const { filterQuery, kqlError } = combineQueries({ - config, - dataProviders, - indexPattern: mockIndexPattern, - browserFields: mockBrowserFields, - filters: [], - kqlQuery: { query: '', language: 'kuery' }, - kqlMode: 'search', - })!; - expect(filterQuery).toMatchInlineSnapshot( - `"{\\"bool\\":{\\"must\\":[],\\"filter\\":[{\\"bool\\":{\\"should\\":[{\\"match\\":{\\"event.end\\":\\"1521848183232\\"}}],\\"minimum_should_match\\":1}}],\\"should\\":[],\\"must_not\\":[]}}"` - ); - expect(kqlError).toBeUndefined(); - }); - - test('Only KQL search/filter query', () => { - const { filterQuery, kqlError } = combineQueries({ - config, - dataProviders: [], - indexPattern: mockIndexPattern, - browserFields: mockBrowserFields, - filters: [], - kqlQuery: { query: 'host.name: "host-1"', language: 'kuery' }, - kqlMode: 'search', - })!; - expect(filterQuery).toEqual( - '{"bool":{"must":[],"filter":[{"bool":{"should":[{"match_phrase":{"host.name":"host-1"}}],"minimum_should_match":1}}],"should":[],"must_not":[]}}' - ); - expect(kqlError).toBeUndefined(); - }); - - test('Invalid KQL search/filter query', () => { - const { filterQuery, kqlError } = combineQueries({ - config, - dataProviders: [], - indexPattern: mockIndexPattern, - browserFields: mockBrowserFields, - filters: [], - kqlQuery: { query: 'host.name: "host-1', language: 'kuery' }, - kqlMode: 'search', - })!; - expect(filterQuery).toBeUndefined(); - expect(kqlError).toBeDefined(); // Not testing on the error message since we don't control changes to them - }); - - test('Data Provider & KQL search query', () => { - const dataProviders = cloneDeep(mockDataProviders.slice(0, 1)); - const { filterQuery, kqlError } = combineQueries({ - config, - dataProviders, - indexPattern: mockIndexPattern, - browserFields: mockBrowserFields, - filters: [], - kqlQuery: { query: 'host.name: "host-1"', language: 'kuery' }, - kqlMode: 'search', - })!; - expect(filterQuery).toEqual( - '{"bool":{"must":[],"filter":[{"bool":{"should":[{"bool":{"should":[{"match_phrase":{"name":"Provider 1"}}],"minimum_should_match":1}},{"bool":{"should":[{"match_phrase":{"host.name":"host-1"}}],"minimum_should_match":1}}],"minimum_should_match":1}}],"should":[],"must_not":[]}}' - ); - expect(kqlError).toBeUndefined(); - }); - - test('Data Provider & KQL filter query', () => { - const dataProviders = cloneDeep(mockDataProviders.slice(0, 1)); - const { filterQuery, kqlError } = combineQueries({ - config, - dataProviders, - indexPattern: mockIndexPattern, - browserFields: mockBrowserFields, - filters: [], - kqlQuery: { query: 'host.name: "host-1"', language: 'kuery' }, - kqlMode: 'filter', - })!; - expect(filterQuery).toEqual( - '{"bool":{"must":[],"filter":[{"bool":{"filter":[{"bool":{"should":[{"match_phrase":{"name":"Provider 1"}}],"minimum_should_match":1}},{"bool":{"should":[{"match_phrase":{"host.name":"host-1"}}],"minimum_should_match":1}}]}}],"should":[],"must_not":[]}}' - ); - expect(kqlError).toBeUndefined(); - }); - - test('Data Provider & KQL search query multiple', () => { - const dataProviders = cloneDeep(mockDataProviders.slice(0, 2)); - dataProviders[0].and = cloneDeep(mockDataProviders.slice(2, 4)); - dataProviders[1].and = cloneDeep(mockDataProviders.slice(4, 5)); - const { filterQuery, kqlError } = combineQueries({ - config, - dataProviders, - indexPattern: mockIndexPattern, - browserFields: mockBrowserFields, - filters: [], - kqlQuery: { query: 'host.name: "host-1"', language: 'kuery' }, - kqlMode: 'search', - })!; - expect(filterQuery).toMatchInlineSnapshot( - `"{\\"bool\\":{\\"must\\":[],\\"filter\\":[{\\"bool\\":{\\"should\\":[{\\"bool\\":{\\"should\\":[{\\"bool\\":{\\"filter\\":[{\\"bool\\":{\\"should\\":[{\\"match_phrase\\":{\\"name\\":\\"Provider 1\\"}}],\\"minimum_should_match\\":1}},{\\"bool\\":{\\"should\\":[{\\"match_phrase\\":{\\"name\\":\\"Provider 3\\"}}],\\"minimum_should_match\\":1}},{\\"bool\\":{\\"should\\":[{\\"match_phrase\\":{\\"name\\":\\"Provider 4\\"}}],\\"minimum_should_match\\":1}}]}},{\\"bool\\":{\\"filter\\":[{\\"bool\\":{\\"should\\":[{\\"match_phrase\\":{\\"name\\":\\"Provider 2\\"}}],\\"minimum_should_match\\":1}},{\\"bool\\":{\\"should\\":[{\\"match_phrase\\":{\\"name\\":\\"Provider 5\\"}}],\\"minimum_should_match\\":1}}]}}],\\"minimum_should_match\\":1}},{\\"bool\\":{\\"should\\":[{\\"match_phrase\\":{\\"host.name\\":\\"host-1\\"}}],\\"minimum_should_match\\":1}}],\\"minimum_should_match\\":1}}],\\"should\\":[],\\"must_not\\":[]}}"` - ); - expect(kqlError).toBeUndefined(); - }); - - test('Data Provider & KQL filter query multiple', () => { - const dataProviders = cloneDeep(mockDataProviders.slice(0, 2)); - dataProviders[0].and = cloneDeep(mockDataProviders.slice(2, 4)); - dataProviders[1].and = cloneDeep(mockDataProviders.slice(4, 5)); - const { filterQuery, kqlError } = combineQueries({ - config, - dataProviders, - indexPattern: mockIndexPattern, - browserFields: mockBrowserFields, - filters: [], - kqlQuery: { query: 'host.name: "host-1"', language: 'kuery' }, - kqlMode: 'filter', - })!; - expect(filterQuery).toMatchInlineSnapshot( - `"{\\"bool\\":{\\"must\\":[],\\"filter\\":[{\\"bool\\":{\\"filter\\":[{\\"bool\\":{\\"should\\":[{\\"bool\\":{\\"filter\\":[{\\"bool\\":{\\"should\\":[{\\"match_phrase\\":{\\"name\\":\\"Provider 1\\"}}],\\"minimum_should_match\\":1}},{\\"bool\\":{\\"should\\":[{\\"match_phrase\\":{\\"name\\":\\"Provider 3\\"}}],\\"minimum_should_match\\":1}},{\\"bool\\":{\\"should\\":[{\\"match_phrase\\":{\\"name\\":\\"Provider 4\\"}}],\\"minimum_should_match\\":1}}]}},{\\"bool\\":{\\"filter\\":[{\\"bool\\":{\\"should\\":[{\\"match_phrase\\":{\\"name\\":\\"Provider 2\\"}}],\\"minimum_should_match\\":1}},{\\"bool\\":{\\"should\\":[{\\"match_phrase\\":{\\"name\\":\\"Provider 5\\"}}],\\"minimum_should_match\\":1}}]}}],\\"minimum_should_match\\":1}},{\\"bool\\":{\\"should\\":[{\\"match_phrase\\":{\\"host.name\\":\\"host-1\\"}}],\\"minimum_should_match\\":1}}]}}],\\"should\\":[],\\"must_not\\":[]}}"` - ); - expect(kqlError).toBeUndefined(); - }); - - test('Data Provider & kql filter query with nested field that exists', () => { - const dataProviders = cloneDeep(mockDataProviders.slice(0, 1)); - const query = combineQueries({ - config, - dataProviders, - indexPattern: mockIndexPattern, - browserFields: mockBrowserFields, - filters: [ - { - meta: { - alias: null, - negate: false, - disabled: false, - type: 'exists', - key: 'nestedField.firstAttributes', - value: 'exists', - }, - query: { - exists: { - field: 'nestedField.firstAttributes', - }, - }, - $state: { - store: FilterStateStore.APP_STATE, - }, - } as Filter, - ], - kqlQuery: { query: '', language: 'kuery' }, - kqlMode: 'filter', - }); - const filterQuery = query && query.filterQuery; - expect(filterQuery).toMatchInlineSnapshot( - `"{\\"bool\\":{\\"must\\":[],\\"filter\\":[{\\"bool\\":{\\"should\\":[{\\"match_phrase\\":{\\"name\\":\\"Provider 1\\"}}],\\"minimum_should_match\\":1}},{\\"exists\\":{\\"field\\":\\"nestedField.firstAttributes\\"}}],\\"should\\":[],\\"must_not\\":[]}}"` - ); - }); - - test('Data Provider & kql filter query with nested field of a particular value', () => { - const dataProviders = cloneDeep(mockDataProviders.slice(0, 1)); - const query = combineQueries({ - config, - dataProviders, - indexPattern: mockIndexPattern, - browserFields: mockBrowserFields, - filters: [ - { - $state: { store: FilterStateStore.APP_STATE }, - meta: { - alias: null, - disabled: false, - key: 'nestedField.secondAttributes', - negate: false, - params: { query: 'test' }, - type: 'phrase', - }, - query: { match_phrase: { 'nestedField.secondAttributes': 'test' } }, - }, - ], - kqlQuery: { query: '', language: 'kuery' }, - kqlMode: 'filter', - }); - const filterQuery = query && query.filterQuery; - expect(filterQuery).toMatchInlineSnapshot( - `"{\\"bool\\":{\\"must\\":[],\\"filter\\":[{\\"bool\\":{\\"should\\":[{\\"match_phrase\\":{\\"name\\":\\"Provider 1\\"}}],\\"minimum_should_match\\":1}},{\\"match_phrase\\":{\\"nestedField.secondAttributes\\":\\"test\\"}}],\\"should\\":[],\\"must_not\\":[]}}"` - ); - }); - - describe('resolverIsShowing', () => { - test('it returns true when graphEventId is NOT an empty string', () => { - expect(resolverIsShowing('a valid id')).toBe(true); - }); - - test('it returns false when graphEventId is undefined', () => { - expect(resolverIsShowing(undefined)).toBe(false); - }); - - test('it returns false when graphEventId is an empty string', () => { - expect(resolverIsShowing('')).toBe(false); - }); - }); - - describe('showGlobalFilters', () => { - test('it returns false when `globalFullScreen` is true and `graphEventId` is NOT an empty string, because Resolver IS showing', () => { - expect(showGlobalFilters({ globalFullScreen: true, graphEventId: 'a valid id' })).toBe(false); - }); - - test('it returns true when `globalFullScreen` is true and `graphEventId` is undefined, because Resolver is NOT showing', () => { - expect(showGlobalFilters({ globalFullScreen: true, graphEventId: undefined })).toBe(true); - }); - - test('it returns true when `globalFullScreen` is true and `graphEventId` is an empty string, because Resolver is NOT showing', () => { - expect(showGlobalFilters({ globalFullScreen: true, graphEventId: '' })).toBe(true); - }); - - test('it returns true when `globalFullScreen` is false and `graphEventId` is NOT an empty string, because Resolver IS showing', () => { - expect(showGlobalFilters({ globalFullScreen: false, graphEventId: 'a valid id' })).toBe(true); - }); - - test('it returns true when `globalFullScreen` is false and `graphEventId` is undefined, because Resolver is NOT showing', () => { - expect(showGlobalFilters({ globalFullScreen: false, graphEventId: undefined })).toBe(true); - }); - - test('it returns true when `globalFullScreen` is false and `graphEventId` is an empty string, because Resolver is NOT showing', () => { - expect(showGlobalFilters({ globalFullScreen: false, graphEventId: '' })).toBe(true); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.tsx index 592130a2db17..9cb0e3d6e60b 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.tsx @@ -8,7 +8,6 @@ import { isEmpty, get } from 'lodash/fp'; import memoizeOne from 'memoize-one'; -import type { DataViewBase, EsQueryConfig, Filter, Query } from '@kbn/es-query'; import { handleSkipFocus, elementOrChildrenHasFocus, @@ -16,7 +15,7 @@ import { getTableSkipFocus, stopPropagationAndPreventDefault, } from '@kbn/timelines-plugin/public'; -import { escapeQueryValue, convertToBuildEsQuery } from '../../../common/lib/keury'; +import { escapeQueryValue } from '../../../common/lib/kuery'; import type { DataProvider, DataProvidersAnd } from './data_providers/data_provider'; import { DataProviderType, EXISTS_OPERATOR } from './data_providers/data_provider'; @@ -134,85 +133,6 @@ export const buildGlobalQuery = (dataProviders: DataProvider[], browserFields: B return !index ? `(${queryMatch})` : `${globalQuery} or (${queryMatch})`; }, ''); -export const combineQueries = ({ - config, - dataProviders, - indexPattern, - browserFields, - filters = [], - kqlQuery, - kqlMode, - isEventViewer, -}: { - config: EsQueryConfig; - dataProviders: DataProvider[]; - indexPattern: DataViewBase; - browserFields: BrowserFields; - filters: Filter[]; - kqlQuery: Query; - kqlMode: string; - isEventViewer?: boolean; -}): { filterQuery?: string; kqlError?: Error } | null => { - const kuery: Query = { query: '', language: kqlQuery.language }; - if (isEmpty(dataProviders) && isEmpty(kqlQuery.query) && isEmpty(filters) && !isEventViewer) { - return null; - } else if ( - isEmpty(dataProviders) && - isEmpty(kqlQuery.query) && - (isEventViewer || !isEmpty(filters)) - ) { - const [filterQuery, kqlError] = convertToBuildEsQuery({ - config, - queries: [kuery], - indexPattern, - filters, - }); - return { - filterQuery, - kqlError, - }; - } else if (isEmpty(dataProviders) && !isEmpty(kqlQuery.query)) { - kuery.query = `(${kqlQuery.query})`; - const [filterQuery, kqlError] = convertToBuildEsQuery({ - config, - queries: [kuery], - indexPattern, - filters, - }); - return { - filterQuery, - kqlError, - }; - } else if (!isEmpty(dataProviders) && isEmpty(kqlQuery)) { - kuery.query = `(${buildGlobalQuery(dataProviders, browserFields)})`; - const [filterQuery, kqlError] = convertToBuildEsQuery({ - config, - queries: [kuery], - indexPattern, - filters, - }); - return { - filterQuery, - kqlError, - }; - } - const operatorKqlQuery = kqlMode === 'filter' ? 'and' : 'or'; - const postpend = (q: string) => `${!isEmpty(q) ? ` ${operatorKqlQuery} (${q})` : ''}`; - kuery.query = `((${buildGlobalQuery(dataProviders, browserFields)})${postpend( - kqlQuery.query as string - )})`; - const [filterQuery, kqlError] = convertToBuildEsQuery({ - config, - queries: [kuery], - indexPattern, - filters, - }); - return { - filterQuery, - kqlError, - }; -}; - /** * The CSS class name of a "stateful event", which appears in both * the `Timeline` and the `Events Viewer` widget diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/index.test.tsx index 3cf7acdef93f..153b24dcf0bf 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/index.test.tsx @@ -11,7 +11,7 @@ import React from 'react'; import { coreMock } from '@kbn/core/public/mocks'; import { DEFAULT_FROM, DEFAULT_TO } from '../../../../../common/constants'; import { mockBrowserFields } from '../../../../common/containers/source/mock'; -import { convertKueryToElasticSearchQuery } from '../../../../common/lib/keury'; +import { convertKueryToElasticSearchQuery } from '../../../../common/lib/kuery'; import { mockIndexPattern, TestProviders } from '../../../../common/mock'; import { QueryBar } from '../../../../common/components/query_bar'; import { FilterStateStore } from '@kbn/es-query'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/index.tsx index ed5b4d6d4515..c040f0c6b1ab 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/index.tsx @@ -18,7 +18,7 @@ import { InputsModelId } from '../../../../common/store/inputs/constants'; import { useSourcererDataView } from '../../../../common/containers/sourcerer'; import { SourcererScopeName } from '../../../../common/store/sourcerer/model'; -import { convertKueryToElasticSearchQuery } from '../../../../common/lib/keury'; +import { convertKueryToElasticSearchQuery } from '../../../../common/lib/kuery'; import type { KqlMode } from '../../../store/timeline/model'; import { useSavedQueryServices } from '../../../../common/utils/saved_query_services'; import type { DispatchUpdateReduxTime } from '../../../../common/components/super_date_picker'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.test.tsx index c7a30a4f501b..b610adbe6da5 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.test.tsx @@ -26,8 +26,8 @@ import { useTimelineEventsDetails } from '../../../containers/details'; import { useSourcererDataView } from '../../../../common/containers/sourcerer'; import { mockSourcererScope } from '../../../../common/containers/sourcerer/mocks'; import { Direction } from '../../../../../common/search_strategy'; -import * as helpers from '../helpers'; import { mockCasesContext } from '@kbn/cases-plugin/public/mocks/mock_cases_context'; +import * as helpers from '../../../../common/lib/kuery'; jest.mock('../../../containers', () => ({ useTimelineEvents: jest.fn(), @@ -47,6 +47,8 @@ jest.mock('../../../../common/containers/sourcerer/use_signal_helpers', () => ({ useSignalHelpers: () => ({ signalIndexNeedsInit: false }), })); +jest.mock('../../../../common/lib/kuery'); + const mockUseResizeObserver: jest.Mock = useResizeObserver as jest.Mock; jest.mock('use-resize-observer/polyfilled'); mockUseResizeObserver.mockImplementation(() => ({})); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx index 5797009535e2..c38304d79841 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx @@ -35,7 +35,8 @@ import { defaultHeaders } from '../body/column_headers/default_headers'; import { StatefulBody } from '../body'; import { Footer, footerHeight } from '../footer'; import { TimelineHeader } from '../header'; -import { calculateTotalPages, combineQueries } from '../helpers'; +import { calculateTotalPages } from '../helpers'; +import { combineQueries } from '../../../../common/lib/kuery'; import { TimelineRefetch } from '../refetch_timeline'; import type { ControlColumnProps, diff --git a/x-pack/plugins/security_solution/public/users/pages/details/index.tsx b/x-pack/plugins/security_solution/public/users/pages/details/index.tsx index 4e2c09380309..906312d6e1ab 100644 --- a/x-pack/plugins/security_solution/public/users/pages/details/index.tsx +++ b/x-pack/plugins/security_solution/public/users/pages/details/index.tsx @@ -31,7 +31,7 @@ import { SiemSearchBar } from '../../../common/components/search_bar'; import { SecuritySolutionPageWrapper } from '../../../common/components/page_wrapper'; import { useGlobalTime } from '../../../common/containers/use_global_time'; import { useKibana } from '../../../common/lib/kibana'; -import { convertToBuildEsQuery } from '../../../common/lib/keury'; +import { convertToBuildEsQuery } from '../../../common/lib/kuery'; import { inputsSelectors } from '../../../common/store'; import { useAlertsPrivileges } from '../../../detections/containers/detection_engine/alerts/use_alerts_privileges'; import { setUsersDetailsTablesActivePageToZero } from '../../store/actions'; diff --git a/x-pack/plugins/security_solution/public/users/pages/users.tsx b/x-pack/plugins/security_solution/public/users/pages/users.tsx index d3140a330da6..df5b891c24e8 100644 --- a/x-pack/plugins/security_solution/public/users/pages/users.tsx +++ b/x-pack/plugins/security_solution/public/users/pages/users.tsx @@ -26,7 +26,7 @@ import { LastEventTime } from '../../common/components/last_event_time'; import { useGlobalFullScreen } from '../../common/containers/use_full_screen'; import { useGlobalTime } from '../../common/containers/use_global_time'; import { useKibana } from '../../common/lib/kibana'; -import { convertToBuildEsQuery } from '../../common/lib/keury'; +import { convertToBuildEsQuery } from '../../common/lib/kuery'; import type { State } from '../../common/store'; import { inputsSelectors } from '../../common/store'; import { setAbsoluteRangeDatePicker } from '../../common/store/inputs/actions'; diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/action_list.test.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/action_list.test.ts index b2402b968ee7..f08b82a49071 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/actions/action_list.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/actions/action_list.test.ts @@ -225,53 +225,171 @@ describe('When using `getActionList()', () => { startDate: 'now-10d', endDate: 'now', commands: ['isolate', 'unisolate', 'get-file'], - userIds: ['elastic'], + userIds: ['*elastic*'], }); expect(esClient.search).toHaveBeenNthCalledWith( 1, - expect.objectContaining({ + { body: { query: { bool: { - filter: [ - { - term: { - input_type: 'endpoint', - }, - }, - { - term: { - type: 'INPUT_ACTION', - }, - }, + must: [ { - range: { - '@timestamp': { - gte: 'now-10d', - }, + bool: { + filter: [ + { + term: { + input_type: 'endpoint', + }, + }, + { + term: { + type: 'INPUT_ACTION', + }, + }, + { + range: { + '@timestamp': { + gte: 'now-10d', + }, + }, + }, + { + range: { + '@timestamp': { + lte: 'now', + }, + }, + }, + { + terms: { + 'data.command': ['isolate', 'unisolate', 'get-file'], + }, + }, + { + terms: { + agents: ['123'], + }, + }, + ], }, }, { - range: { - '@timestamp': { - lte: 'now', - }, - }, - }, - { - terms: { - 'data.command': ['isolate', 'unisolate', 'get-file'], + bool: { + should: [ + { + query_string: { + fields: ['user_id'], + query: '*elastic*', + }, + }, + ], + minimum_should_match: 1, }, }, + ], + }, + }, + sort: [ + { + '@timestamp': { + order: 'desc', + }, + }, + ], + }, + from: 0, + index: '.logs-endpoint.actions-default', + size: 20, + }, + { ignore: [404], meta: true } + ); + }); + + it('should call search with exact usernames when no wildcards are present', async () => { + // mock metadataService.findHostMetadataForFleetAgents resolved value + (endpointAppContextService.getEndpointMetadataService as jest.Mock) = jest + .fn() + .mockReturnValue({ + findHostMetadataForFleetAgents: jest.fn().mockResolvedValue([]), + }); + await getActionList({ + esClient, + logger, + metadataService: endpointAppContextService.getEndpointMetadataService(), + pageSize: 10, + startDate: 'now-1d', + endDate: 'now', + userIds: ['elastic', 'kibana'], + }); + + expect(esClient.search).toHaveBeenNthCalledWith( + 1, + { + body: { + query: { + bool: { + must: [ { - terms: { - user_id: ['elastic'], + bool: { + filter: [ + { + term: { + input_type: 'endpoint', + }, + }, + { + term: { + type: 'INPUT_ACTION', + }, + }, + { + range: { + '@timestamp': { + gte: 'now-1d', + }, + }, + }, + { + range: { + '@timestamp': { + lte: 'now', + }, + }, + }, + ], }, }, { - terms: { - agents: ['123'], + bool: { + should: [ + { + bool: { + should: [ + { + match: { + user_id: 'elastic', + }, + }, + ], + minimum_should_match: 1, + }, + }, + { + bool: { + should: [ + { + match: { + user_id: 'kibana', + }, + }, + ], + minimum_should_match: 1, + }, + }, + ], + minimum_should_match: 1, }, }, ], @@ -287,12 +405,9 @@ describe('When using `getActionList()', () => { }, from: 0, index: '.logs-endpoint.actions-default', - size: 20, - }), - expect.objectContaining({ - ignore: [404], - meta: true, - }) + size: 10, + }, + { ignore: [404], meta: true } ); }); diff --git a/x-pack/plugins/security_solution/server/endpoint/utils/action_list_helpers.test.ts b/x-pack/plugins/security_solution/server/endpoint/utils/action_list_helpers.test.ts index 9ea0e66a510c..b2145bc8740f 100644 --- a/x-pack/plugins/security_solution/server/endpoint/utils/action_list_helpers.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/utils/action_list_helpers.test.ts @@ -33,15 +33,21 @@ describe('action helpers', () => { body: { query: { bool: { - filter: [ - { - term: { - input_type: 'endpoint', - }, - }, + must: [ { - term: { - type: 'INPUT_ACTION', + bool: { + filter: [ + { + term: { + input_type: 'endpoint', + }, + }, + { + term: { + type: 'INPUT_ACTION', + }, + }, + ], }, }, ], @@ -65,6 +71,7 @@ describe('action helpers', () => { } ); }); + it('should query with additional filter options provided', async () => { const esClient = mockScopedEsClient.asInternalUser; @@ -77,7 +84,7 @@ describe('action helpers', () => { elasticAgentIds: ['agent-123', 'agent-456'], endDate: 'now', commands: ['isolate', 'unisolate', 'get-file'], - userIds: ['elastic'], + userIds: ['*elastic*', '*kibana*'], }); expect(esClient.search).toHaveBeenCalledWith( @@ -85,44 +92,180 @@ describe('action helpers', () => { body: { query: { bool: { - filter: [ + must: [ { - term: { - input_type: 'endpoint', + bool: { + filter: [ + { + term: { + input_type: 'endpoint', + }, + }, + { + term: { + type: 'INPUT_ACTION', + }, + }, + { + range: { + '@timestamp': { + gte: 'now-10d', + }, + }, + }, + { + range: { + '@timestamp': { + lte: 'now', + }, + }, + }, + { + terms: { + 'data.command': ['isolate', 'unisolate', 'get-file'], + }, + }, + { + terms: { + agents: ['agent-123', 'agent-456'], + }, + }, + ], }, }, { - term: { - type: 'INPUT_ACTION', - }, - }, - { - range: { - '@timestamp': { - gte: 'now-10d', - }, - }, - }, - { - range: { - '@timestamp': { - lte: 'now', - }, - }, - }, - { - terms: { - 'data.command': ['isolate', 'unisolate', 'get-file'], + bool: { + should: [ + { + bool: { + should: [ + { + query_string: { + fields: ['user_id'], + query: '*elastic*', + }, + }, + ], + minimum_should_match: 1, + }, + }, + { + bool: { + should: [ + { + query_string: { + fields: ['user_id'], + query: '*kibana*', + }, + }, + ], + minimum_should_match: 1, + }, + }, + ], + minimum_should_match: 1, }, }, + ], + }, + }, + sort: [ + { + '@timestamp': { + order: 'desc', + }, + }, + ], + }, + from: 5, + index: '.logs-endpoint.actions-default', + size: 20, + }, + { + ignore: [404], + meta: true, + } + ); + }); + + it('should search with exact usernames when given', async () => { + const esClient = mockScopedEsClient.asInternalUser; + + applyActionListEsSearchMock(esClient); + await getActions({ + esClient, + size: 10, + from: 1, + startDate: 'now-1d', + endDate: 'now', + userIds: ['elastic', 'kibana'], + }); + + expect(esClient.search).toHaveBeenCalledWith( + { + body: { + query: { + bool: { + must: [ { - terms: { - user_id: ['elastic'], + bool: { + filter: [ + { + term: { + input_type: 'endpoint', + }, + }, + { + term: { + type: 'INPUT_ACTION', + }, + }, + { + range: { + '@timestamp': { + gte: 'now-1d', + }, + }, + }, + { + range: { + '@timestamp': { + lte: 'now', + }, + }, + }, + ], }, }, { - terms: { - agents: ['agent-123', 'agent-456'], + bool: { + should: [ + { + bool: { + should: [ + { + match: { + user_id: 'elastic', + }, + }, + ], + minimum_should_match: 1, + }, + }, + { + bool: { + should: [ + { + match: { + user_id: 'kibana', + }, + }, + ], + minimum_should_match: 1, + }, + }, + ], + minimum_should_match: 1, }, }, ], @@ -136,9 +279,9 @@ describe('action helpers', () => { }, ], }, - from: 5, + from: 1, index: '.logs-endpoint.actions-default', - size: 20, + size: 10, }, { ignore: [404], @@ -146,6 +289,7 @@ describe('action helpers', () => { } ); }); + it('should return expected output', async () => { const esClient = mockScopedEsClient.asInternalUser; const actionRequests = createActionRequestsEsSearchResultsMock(); diff --git a/x-pack/plugins/security_solution/server/endpoint/utils/action_list_helpers.ts b/x-pack/plugins/security_solution/server/endpoint/utils/action_list_helpers.ts index a522cbde6d9e..ee0a2d6e6e2c 100644 --- a/x-pack/plugins/security_solution/server/endpoint/utils/action_list_helpers.ts +++ b/x-pack/plugins/security_solution/server/endpoint/utils/action_list_helpers.ts @@ -9,6 +9,7 @@ import type { ElasticsearchClient } from '@kbn/core/server'; import type { SearchRequest } from '@kbn/data-plugin/public'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { TransportResult } from '@elastic/elasticsearch'; +import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query'; import { ENDPOINT_ACTIONS_INDEX } from '../../../common/endpoint/constants'; import type { @@ -49,10 +50,6 @@ export const getActions = async ({ }); } - if (userIds?.length) { - additionalFilters.push({ terms: { user_id: userIds } }); - } - if (elasticAgentIds?.length) { additionalFilters.push({ terms: { agents: elasticAgentIds } }); } @@ -70,15 +67,27 @@ export const getActions = async ({ ...additionalFilters, ]; + const must: SearchRequest = [ + { + bool: { + filter: actionsFilters, + }, + }, + ]; + + if (userIds?.length) { + const userIdsKql = userIds.map((userId) => `user_id:${userId}`).join(' or '); + const mustClause = toElasticsearchQuery(fromKueryExpression(userIdsKql)); + must.push(mustClause); + } + const actionsSearchQuery: SearchRequest = { index: ENDPOINT_ACTIONS_INDEX, size, from, body: { query: { - bool: { - filter: actionsFilters, - }, + bool: { must }, }, sort: [ { diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/helpers.test.ts b/x-pack/plugins/security_solution/server/lib/telemetry/helpers.test.ts index 330add442519..cba00773453f 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/helpers.test.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/helpers.test.ts @@ -933,7 +933,8 @@ describe('test tlog', () => { }); }); -describe('test create task metrics', () => { +// FLAKY: https://github.com/elastic/kibana/issues/141356 +describe.skip('test create task metrics', () => { test('can succeed when all parameters are given', async () => { const stubTaskName = 'test'; const stubPassed = true; diff --git a/x-pack/plugins/stack_alerts/public/alert_types/threshold/index_threshold_api.ts b/x-pack/plugins/stack_alerts/public/alert_types/threshold/index_threshold_api.ts index a884d2230c2b..d1e5613c659f 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/threshold/index_threshold_api.ts +++ b/x-pack/plugins/stack_alerts/public/alert_types/threshold/index_threshold_api.ts @@ -9,7 +9,7 @@ import { HttpSetup } from '@kbn/core/public'; import { TimeSeriesResult } from '@kbn/triggers-actions-ui-plugin/common'; import { IndexThresholdAlertParams } from './types'; -const INDEX_THRESHOLD_DATA_API_ROOT = '/api/triggers_actions_ui/data'; +const INDEX_THRESHOLD_DATA_API_ROOT = '/internal/triggers_actions_ui/data'; export interface GetThresholdAlertVisualizationDataParams { model: IndexThresholdAlertParams; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/ping_list/columns/ping_timestamp/ping_timestamp.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/ping_list/columns/ping_timestamp/ping_timestamp.tsx index 1eef87ed9efc..4cee2eb9bfca 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/ping_list/columns/ping_timestamp/ping_timestamp.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/ping_list/columns/ping_timestamp/ping_timestamp.tsx @@ -63,8 +63,11 @@ export const PingTimestamp = ({ const [screenshotRef, setScreenshotRef] = useState(undefined); + const isScreenshotRefValid = Boolean( + screenshotRef && screenshotRef?.ref?.screenshotRef?.synthetics?.step?.index === stepNumber + ); const { data, loading } = useInProgressImage({ - hasImage: Boolean(stepImages[stepNumber - 1]) || Boolean(screenshotRef), + hasImage: Boolean(stepImages[stepNumber - 1]) || isScreenshotRefValid, hasIntersected: Boolean(intersection && intersection.intersectionRatio === 1), stepStatus, imgPath, diff --git a/x-pack/plugins/timelines/public/components/t_grid/helpers.test.tsx b/x-pack/plugins/timelines/public/components/t_grid/helpers.test.tsx index d0d656d64e4d..288ad46d326f 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/helpers.test.tsx +++ b/x-pack/plugins/timelines/public/components/t_grid/helpers.test.tsx @@ -526,6 +526,101 @@ describe('Combined Queries', () => { ); }); + test('Disabled Data Provider and kqlQuery', () => { + const dataProviders = cloneDeep(mockDataProviders.slice(0, 1)); + dataProviders[0].enabled = false; + const { filterQuery } = combineQueries({ + config, + dataProviders, + indexPattern: mockIndexPattern, + browserFields: mockBrowserFields, + filters: [], + kqlQuery: { query: '_id:*', language: 'kuery' }, + kqlMode: 'search', + })!; + + const expectQueryString = JSON.stringify({ + bool: { + must: [], + filter: [ + { + bool: { + should: [ + { + exists: { + field: '_id', + }, + }, + ], + minimum_should_match: 1, + }, + }, + ], + should: [], + must_not: [], + }, + }); + + expect(filterQuery).toStrictEqual(expectQueryString); + }); + + test('Both disabled & enabled data provider and kqlQuery', () => { + const dataProviders = cloneDeep(mockDataProviders.slice(0, 2)); + dataProviders[0].enabled = false; + const { filterQuery } = combineQueries({ + config, + dataProviders, + indexPattern: mockIndexPattern, + browserFields: mockBrowserFields, + filters: [], + kqlQuery: { query: '_id:*', language: 'kuery' }, + kqlMode: 'search', + })!; + + const expectQueryString = JSON.stringify({ + bool: { + must: [], + filter: [ + { + bool: { + should: [ + { + bool: { + should: [ + { + match_phrase: { + [dataProviders[1].queryMatch.field]: dataProviders[1].queryMatch.value, + }, + }, + ], + minimum_should_match: 1, + }, + }, + { + bool: { + should: [ + { + exists: { + field: '_id', + }, + }, + ], + minimum_should_match: 1, + }, + }, + ], + minimum_should_match: 1, + }, + }, + ], + should: [], + must_not: [], + }, + }); + + expect(filterQuery).toStrictEqual(expectQueryString); + }); + describe('resolverIsShowing', () => { test('it returns true when graphEventId is NOT an empty string', () => { expect(resolverIsShowing('a valid id')).toBe(true); diff --git a/x-pack/plugins/timelines/public/components/t_grid/helpers.tsx b/x-pack/plugins/timelines/public/components/t_grid/helpers.tsx index 4de43ef24939..497787166510 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/helpers.tsx +++ b/x-pack/plugins/timelines/public/components/t_grid/helpers.tsx @@ -144,6 +144,10 @@ interface CombineQueries { kqlMode: string; } +export const isDataProviderEmpty = (dataProviders: DataProvider[]) => { + return isEmpty(dataProviders) || isEmpty(dataProviders.filter((d) => d.enabled === true)); +}; + export const combineQueries = ({ config, dataProviders, @@ -154,9 +158,9 @@ export const combineQueries = ({ kqlMode, }: CombineQueries): { filterQuery: string | undefined; kqlError: Error | undefined } | null => { const kuery: Query = { query: '', language: kqlQuery.language }; - if (isEmpty(dataProviders) && isEmpty(kqlQuery.query) && isEmpty(filters)) { + if (isDataProviderEmpty(dataProviders) && isEmpty(kqlQuery.query) && isEmpty(filters)) { return null; - } else if (isEmpty(dataProviders) && isEmpty(kqlQuery.query) && !isEmpty(filters)) { + } else if (isDataProviderEmpty(dataProviders) && isEmpty(kqlQuery.query) && !isEmpty(filters)) { const [filterQuery, kqlError] = convertToBuildEsQuery({ config, queries: [kuery], @@ -168,40 +172,21 @@ export const combineQueries = ({ filterQuery, kqlError, }; - } else if (isEmpty(dataProviders) && !isEmpty(kqlQuery.query)) { - kuery.query = `(${kqlQuery.query})`; + } - const [filterQuery, kqlError] = convertToBuildEsQuery({ - config, - queries: [kuery], - indexPattern, - filters, - }); + const operatorKqlQuery = kqlMode === 'filter' ? 'and' : 'or'; - return { - filterQuery, - kqlError, - }; - } else if (!isEmpty(dataProviders) && isEmpty(kqlQuery)) { - kuery.query = `(${buildGlobalQuery(dataProviders, browserFields)})`; + const postpend = (q: string) => `${!isEmpty(q) ? `(${q})` : ''}`; - const [filterQuery, kqlError] = convertToBuildEsQuery({ - config, - queries: [kuery], - indexPattern, - filters, - }); + const globalQuery = buildGlobalQuery(dataProviders, browserFields); // based on Data Providers - return { - filterQuery, - kqlError, - }; - } - const operatorKqlQuery = kqlMode === 'filter' ? 'and' : 'or'; - const postpend = (q: string) => `${!isEmpty(q) ? ` ${operatorKqlQuery} (${q})` : ''}`; - kuery.query = `((${buildGlobalQuery(dataProviders, browserFields)})${postpend( - kqlQuery.query as string - )})`; + const querySuffix = postpend(kqlQuery.query as string); // based on Unified Search bar + + const queryPrefix = globalQuery ? `(${globalQuery})` : ''; + + const queryOperator = queryPrefix && querySuffix ? operatorKqlQuery : ''; + + kuery.query = `(${queryPrefix} ${queryOperator} ${querySuffix})`; const [filterQuery, kqlError] = convertToBuildEsQuery({ config, diff --git a/x-pack/plugins/timelines/public/index.ts b/x-pack/plugins/timelines/public/index.ts index b31f01c0663c..8ed9bc7cd092 100644 --- a/x-pack/plugins/timelines/public/index.ts +++ b/x-pack/plugins/timelines/public/index.ts @@ -95,3 +95,5 @@ export { StatefulEventContext } from './components/stateful_event_context'; export { TimelineContext } from './components/t_grid/shared'; export type { AddToTimelineButtonProps } from './components/hover_actions/actions/add_to_timeline'; + +export { combineQueries } from './components/t_grid/helpers'; diff --git a/x-pack/plugins/triggers_actions_ui/common/index.ts b/x-pack/plugins/triggers_actions_ui/common/index.ts index bc9592a2e49f..1675dee6129d 100644 --- a/x-pack/plugins/triggers_actions_ui/common/index.ts +++ b/x-pack/plugins/triggers_actions_ui/common/index.ts @@ -9,6 +9,6 @@ /* eslint-disable @kbn/eslint/no_export_all */ export * from './data'; -export const BASE_TRIGGERS_ACTIONS_UI_API_PATH = '/api/triggers_actions_ui'; +export const BASE_TRIGGERS_ACTIONS_UI_API_PATH = '/internal/triggers_actions_ui'; export * from './parse_interval'; export * from './experimental_features'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty.tsx index 53b41ea49a27..8c3961e6d711 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty.tsx @@ -16,6 +16,7 @@ import { EventActionOptions, } from '../types'; import { hasMustacheTokens } from '../../../lib/has_mustache_tokens'; +import { AlertProvidedActionVariables } from '../../../lib/action_variables'; export function getActionType(): ActionTypeModel< PagerDutyConfig, @@ -83,6 +84,14 @@ export function getActionType(): ActionTypeModel< }, actionConnectorFields: lazy(() => import('./pagerduty_connectors')), actionParamsFields: lazy(() => import('./pagerduty_params')), + defaultActionParams: { + dedupKey: `{{${AlertProvidedActionVariables.ruleId}}}:{{${AlertProvidedActionVariables.alertId}}}`, + eventAction: EventActionOptions.TRIGGER, + }, + defaultRecoveredActionParams: { + dedupKey: `{{${AlertProvidedActionVariables.ruleId}}}:{{${AlertProvidedActionVariables.alertId}}}`, + eventAction: EventActionOptions.RESOLVE, + }, }; } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/xmatters/xmatters.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/xmatters/xmatters.tsx index 2ba8d78d90ae..9f2955479d52 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/xmatters/xmatters.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/xmatters/xmatters.tsx @@ -9,6 +9,7 @@ import { lazy } from 'react'; import { i18n } from '@kbn/i18n'; import { ActionTypeModel, GenericValidationResult } from '../../../../types'; import { XmattersActionParams, XmattersConfig, XmattersSecrets } from '../types'; +import { AlertProvidedActionVariables } from '../../../lib/action_variables'; export function getActionType(): ActionTypeModel< XmattersConfig, @@ -45,5 +46,12 @@ export function getActionType(): ActionTypeModel< }, actionConnectorFields: lazy(() => import('./xmatters_connectors')), actionParamsFields: lazy(() => import('./xmatters_params')), + defaultActionParams: { + alertActionGroupName: `{{${AlertProvidedActionVariables.alertActionGroupName}}}`, + signalId: `{{${AlertProvidedActionVariables.ruleId}}}:{{${AlertProvidedActionVariables.alertId}}}`, + ruleName: `{{${AlertProvidedActionVariables.ruleName}}}`, + date: `{{${AlertProvidedActionVariables.date}}}`, + spaceId: `{{${AlertProvidedActionVariables.ruleSpaceId}}}`, + }, }; } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/get_defaults_for_action_params.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/get_defaults_for_action_params.test.ts deleted file mode 100644 index fa4a5b6b2b33..000000000000 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/get_defaults_for_action_params.test.ts +++ /dev/null @@ -1,26 +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 { RecoveredActionGroup } from '@kbn/alerting-plugin/common'; -import { AlertProvidedActionVariables } from './action_variables'; -import { getDefaultsForActionParams } from './get_defaults_for_action_params'; - -describe('getDefaultsForActionParams', () => { - test('pagerduty defaults', async () => { - expect(getDefaultsForActionParams('.pagerduty', 'test', false)).toEqual({ - dedupKey: `{{${AlertProvidedActionVariables.ruleId}}}:{{${AlertProvidedActionVariables.alertId}}}`, - eventAction: 'trigger', - }); - }); - - test('pagerduty defaults for recovered action group', async () => { - expect(getDefaultsForActionParams('.pagerduty', RecoveredActionGroup.id, true)).toEqual({ - dedupKey: `{{${AlertProvidedActionVariables.ruleId}}}:{{${AlertProvidedActionVariables.alertId}}}`, - eventAction: 'resolve', - }); - }); -}); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/get_defaults_for_action_params.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/get_defaults_for_action_params.ts deleted file mode 100644 index d3f60f4c6335..000000000000 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/get_defaults_for_action_params.ts +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { RuleActionParam } from '@kbn/alerting-plugin/common'; -import { EventActionOptions } from '../components/builtin_action_types/types'; -import { AlertProvidedActionVariables } from './action_variables'; - -export type DefaultActionParams = Record | undefined; -export type DefaultActionParamsGetter = ( - actionTypeId: string, - actionGroupId: string -) => DefaultActionParams; -export const getDefaultsForActionParams = ( - actionTypeId: string, - actionGroupId: string, - isRecoveryActionGroup: boolean -): DefaultActionParams => { - switch (actionTypeId) { - case '.pagerduty': - const pagerDutyDefaults = { - dedupKey: `{{${AlertProvidedActionVariables.ruleId}}}:{{${AlertProvidedActionVariables.alertId}}}`, - eventAction: EventActionOptions.TRIGGER, - }; - if (isRecoveryActionGroup) { - pagerDutyDefaults.eventAction = EventActionOptions.RESOLVE; - } - return pagerDutyDefaults; - case '.xmatters': - const xmattersDefaults = { - alertActionGroupName: `{{${AlertProvidedActionVariables.alertActionGroupName}}}`, - signalId: `{{${AlertProvidedActionVariables.ruleId}}}:{{${AlertProvidedActionVariables.alertId}}}`, - ruleName: `{{${AlertProvidedActionVariables.ruleName}}}`, - date: `{{${AlertProvidedActionVariables.date}}}`, - spaceId: `{{${AlertProvidedActionVariables.ruleSpaceId}}}`, - }; - return xmattersDefaults; - } -}; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx index 06978dc8c13e..8f34a8ad07ed 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx @@ -37,7 +37,6 @@ import { actionTypeCompare } from '../../lib/action_type_compare'; import { checkActionFormActionTypeEnabled } from '../../lib/check_action_type_enabled'; import { VIEW_LICENSE_OPTIONS_LINK } from '../../../common/constants'; import { useKibana } from '../../../common/lib/kibana'; -import { DefaultActionParamsGetter } from '../../lib/get_defaults_for_action_params'; import { ConnectorAddModal } from '.'; import { suspendedComponentWithProps } from '../../lib/suspended_component_with_props'; import { OmitMessageVariablesType } from '../../lib/action_variables'; @@ -61,7 +60,7 @@ export interface ActionAccordionFormProps { setHasActionsDisabled?: (value: boolean) => void; setHasActionsWithBrokenConnector?: (value: boolean) => void; actionTypeRegistry: ActionTypeRegistryContract; - getDefaultActionParams?: DefaultActionParamsGetter; + recoveryActionGroup?: string; isActionGroupDisabledForActionType?: (actionGroupId: string, actionTypeId: string) => boolean; } @@ -84,7 +83,7 @@ export const ActionForm = ({ setHasActionsDisabled, setHasActionsWithBrokenConnector, actionTypeRegistry, - getDefaultActionParams, + recoveryActionGroup, isActionGroupDisabledForActionType, }: ActionAccordionFormProps) => { const { @@ -361,7 +360,7 @@ export const ActionForm = ({ messageVariables={messageVariables} actionGroups={actionGroups} defaultActionMessage={defaultActionMessage} - defaultParams={getDefaultActionParams?.(actionItem.actionTypeId, actionItem.group)} + recoveryActionGroup={recoveryActionGroup} isActionGroupDisabledForActionType={isActionGroupDisabledForActionType} setActionGroupIdByIndex={setActionGroupIdByIndex} onAddConnector={() => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.test.tsx index eb710b884e13..0112f2296ee0 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.test.tsx @@ -11,7 +11,6 @@ import { actionTypeRegistryMock } from '../../action_type_registry.mock'; import { ActionConnector, ActionType, RuleAction, GenericValidationResult } from '../../../types'; import { act } from 'react-dom/test-utils'; import { EuiFieldText } from '@elastic/eui'; -import { DefaultActionParams } from '../../lib/get_defaults_for_action_params'; jest.mock('../../../common/lib/kibana'); const actionTypeRegistry = actionTypeRegistryMock.create(); @@ -43,6 +42,10 @@ describe('action_type_form', () => { }, actionConnectorFields: null, actionParamsFields: mockedActionParamsFields, + defaultActionParams: { + dedupKey: 'test', + eventAction: 'resolve', + }, }); actionTypeRegistry.get.mockReturnValue(actionType); @@ -89,6 +92,10 @@ describe('action_type_form', () => { }, actionConnectorFields: null, actionParamsFields: mockedActionParamsFields, + defaultActionParams: { + dedupKey: 'test', + eventAction: 'resolve', + }, }); actionTypeRegistry.get.mockReturnValue(actionType); @@ -134,6 +141,10 @@ describe('action_type_form', () => { }, actionConnectorFields: null, actionParamsFields: mockedActionParamsFields, + defaultActionParams: { + dedupKey: 'test', + eventAction: 'resolve', + }, }); actionTypeRegistry.get.mockReturnValue(actionType); @@ -188,7 +199,6 @@ function getActionTypeForm( defaultActionGroupId?: string, connectors?: Array, Record>>, actionTypeIndex?: Record, - defaultParams?: DefaultActionParams, onAddConnector?: () => void, onDeleteAction?: () => void, onConnectorSelected?: (id: string) => void @@ -259,10 +269,6 @@ function getActionTypeForm( }, }; - const defaultParamsDefault = { - dedupKey: `test`, - eventAction: 'resolve', - }; return ( ); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.tsx index aba42984de1b..c3ca4b027561 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.tsx @@ -42,7 +42,6 @@ import { hasSaveActionsCapability } from '../../lib/capabilities'; import { ActionAccordionFormProps, ActionGroupWithMessageVariables } from './action_form'; import { transformActionVariables } from '../../lib/action_variables'; import { useKibana } from '../../../common/lib/kibana'; -import { DefaultActionParams } from '../../lib/get_defaults_for_action_params'; import { ConnectorsSelection } from './connectors_selection'; export type ActionTypeFormProps = { @@ -56,7 +55,7 @@ export type ActionTypeFormProps = { actionTypesIndex: ActionTypeIndex; connectors: ActionConnector[]; actionTypeRegistry: ActionTypeRegistryContract; - defaultParams: DefaultActionParams; + recoveryActionGroup?: string; isActionGroupDisabledForActionType?: (actionGroupId: string, actionTypeId: string) => boolean; } & Pick< ActionAccordionFormProps, @@ -92,7 +91,7 @@ export const ActionTypeForm = ({ setActionGroupIdByIndex, actionTypeRegistry, isActionGroupDisabledForActionType, - defaultParams, + recoveryActionGroup, }: ActionTypeFormProps) => { const { application: { capabilities }, @@ -107,26 +106,47 @@ export const ActionTypeForm = ({ errors: {}, }); + const getDefaultParams = async () => { + const connectorType = await actionTypeRegistry.get(actionItem.actionTypeId); + let defaultParams; + if (actionItem.group === recoveryActionGroup) { + defaultParams = connectorType.defaultRecoveredActionParams; + } + + if (!defaultParams) { + defaultParams = connectorType.defaultActionParams; + } + + return defaultParams; + }; + useEffect(() => { - setAvailableActionVariables( - messageVariables ? getAvailableActionVariables(messageVariables, selectedActionGroup) : [] - ); - if (defaultParams) { - for (const [key, paramValue] of Object.entries(defaultParams)) { - if (actionItem.params[key] === undefined || actionItem.params[key] === null) { - setActionParamsProperty(key, paramValue, index); + (async () => { + setAvailableActionVariables( + messageVariables ? getAvailableActionVariables(messageVariables, selectedActionGroup) : [] + ); + + const defaultParams = await getDefaultParams(); + if (defaultParams) { + for (const [key, paramValue] of Object.entries(defaultParams)) { + if (actionItem.params[key] === undefined || actionItem.params[key] === null) { + setActionParamsProperty(key, paramValue, index); + } } } - } + })(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [actionItem.group]); useEffect(() => { - if (defaultParams && actionGroup) { - for (const [key, paramValue] of Object.entries(defaultParams)) { - setActionParamsProperty(key, paramValue, index); + (async () => { + const defaultParams = await getDefaultParams(); + if (defaultParams && actionGroup) { + for (const [key, paramValue] of Object.entries(defaultParams)) { + setActionParamsProperty(key, paramValue, index); + } } - } + })(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [actionGroup]); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.tsx index 6d1e614c428d..9a1162b89a43 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.tsx @@ -68,7 +68,6 @@ import { SolutionFilter } from './solution_filter'; import './rule_form.scss'; import { useKibana } from '../../../common/lib/kibana'; import { recoveredActionGroupMessage } from '../../constants'; -import { getDefaultsForActionParams } from '../../lib/get_defaults_for_action_params'; import { IsEnabledResult, IsDisabledResult } from '../../lib/check_rule_type_enabled'; import { RuleNotifyWhen } from './rule_notify_when'; import { checkRuleTypeEnabled } from '../../lib/check_rule_type_enabled'; @@ -309,15 +308,6 @@ export const RuleForm = ({ const selectedRuleType = rule?.ruleTypeId ? ruleTypeIndex?.get(rule?.ruleTypeId) : undefined; const recoveryActionGroup = selectedRuleType?.recoveryActionGroup?.id; - const getDefaultActionParams = useCallback( - (actionTypeId: string, actionGroupId: string): Record | undefined => - getDefaultsForActionParams( - actionTypeId, - actionGroupId, - actionGroupId === recoveryActionGroup - ), - [recoveryActionGroup] - ); const tagsOptions = rule.tags ? rule.tags.map((label: string) => ({ label })) : []; @@ -571,7 +561,7 @@ export const RuleForm = ({ } : { ...actionGroup, defaultActionMessage: ruleTypeModel?.defaultActionMessage } )} - getDefaultActionParams={getDefaultActionParams} + recoveryActionGroup={recoveryActionGroup} setActionIdByIndex={(id: string, index: number) => setActionProperty('id', id, index)} setActionGroupIdByIndex={(group: string, index: number) => setActionProperty('group', group, index) diff --git a/x-pack/plugins/triggers_actions_ui/public/common/lib/config_api.test.ts b/x-pack/plugins/triggers_actions_ui/public/common/lib/config_api.test.ts index 765c6b2b7b12..9f117b51c9cf 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/lib/config_api.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/common/lib/config_api.test.ts @@ -17,7 +17,7 @@ describe('triggersActionsUiConfig', () => { expect(http.get.mock.calls).toMatchInlineSnapshot(` Array [ Array [ - "/api/triggers_actions_ui/_config", + "/internal/triggers_actions_ui/_config", ], ] `); diff --git a/x-pack/plugins/triggers_actions_ui/public/common/lib/data_apis.test.ts b/x-pack/plugins/triggers_actions_ui/public/common/lib/data_apis.test.ts index 178c891dc3a3..3f08c3e5f844 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/lib/data_apis.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/common/lib/data_apis.test.ts @@ -38,7 +38,7 @@ describe('Data API', () => { http.post.mockResolvedValueOnce(mockFields); const fields = await getESIndexFields({ indexes, http }); - expect(http.post).toHaveBeenCalledWith('/api/triggers_actions_ui/data/_fields', { + expect(http.post).toHaveBeenCalledWith('/internal/triggers_actions_ui/data/_fields', { body: `{"indexPatterns":${JSON.stringify(indexes)}}`, }); expect(fields).toEqual(mockFields.fields); @@ -50,7 +50,7 @@ describe('Data API', () => { http.post.mockResolvedValueOnce(mockIndices); const indices = await getMatchingIndices({ pattern, http }); - expect(http.post).toHaveBeenCalledWith('/api/triggers_actions_ui/data/_indices', { + expect(http.post).toHaveBeenCalledWith('/internal/triggers_actions_ui/data/_indices', { body: `{"pattern":"*${mockPattern}*"}`, }); expect(indices).toEqual(mockIndices.indices); diff --git a/x-pack/plugins/triggers_actions_ui/public/common/lib/data_apis.ts b/x-pack/plugins/triggers_actions_ui/public/common/lib/data_apis.ts index 90f80dd3dc2f..fa4bf0a86986 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/lib/data_apis.ts +++ b/x-pack/plugins/triggers_actions_ui/public/common/lib/data_apis.ts @@ -8,7 +8,7 @@ import { HttpSetup } from '@kbn/core/public'; import { DataViewsContract, DataView } from '@kbn/data-views-plugin/public'; -const DATA_API_ROOT = '/api/triggers_actions_ui/data'; +const DATA_API_ROOT = '/internal/triggers_actions_ui/data'; const formatPattern = (pattern: string) => { let formattedPattern = pattern; diff --git a/x-pack/plugins/triggers_actions_ui/public/common/lib/health_api.test.ts b/x-pack/plugins/triggers_actions_ui/public/common/lib/health_api.test.ts index 6454f726e934..b668d58f5b7d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/lib/health_api.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/common/lib/health_api.test.ts @@ -17,7 +17,7 @@ describe('triggersActionsUiHealth', () => { expect(http.get.mock.calls).toMatchInlineSnapshot(` Array [ Array [ - "/api/triggers_actions_ui/_health", + "/internal/triggers_actions_ui/_health", ], ] `); diff --git a/x-pack/plugins/triggers_actions_ui/public/types.ts b/x-pack/plugins/triggers_actions_ui/public/types.ts index ae827a5b33f7..65b6d50e2228 100644 --- a/x-pack/plugins/triggers_actions_ui/public/types.ts +++ b/x-pack/plugins/triggers_actions_ui/public/types.ts @@ -204,6 +204,8 @@ export interface ActionTypeModel > | null; actionParamsFields: React.LazyExoticComponent>>; + defaultActionParams?: Partial; + defaultRecoveredActionParams?: Partial; customConnectorSelectItem?: CustomConnectorSelectionItem; isExperimental?: boolean; } diff --git a/x-pack/plugins/triggers_actions_ui/server/data/README.md b/x-pack/plugins/triggers_actions_ui/server/data/README.md index 78577f078300..696232d5a680 100644 --- a/x-pack/plugins/triggers_actions_ui/server/data/README.md +++ b/x-pack/plugins/triggers_actions_ui/server/data/README.md @@ -6,9 +6,9 @@ The TriggersActionsUi plugin's Data Apis back the functionality needed by the In The following endpoints are provided for this alert type: -- `POST /api/triggers_actions_ui/data/_indices` -- `POST /api/triggers_actions_ui/data/_fields` -- `POST /api/triggers_actions_ui/data/_time_series_query` +- `POST /internal/triggers_actions_ui/data/_indices` +- `POST /internal/triggers_actions_ui/data/_fields` +- `POST /internal/triggers_actions_ui/data/_time_series_query` ### `POST .../_indices` @@ -79,7 +79,7 @@ provide a "preview" of the alert during creation/editing based on recent data, and could be used to show a "simulation" of the the alert over an arbitrary range of time. -The endpoint is `POST /api/triggers_actions_ui/data/_time_series_query`. +The endpoint is `POST /internal/triggers_actions_ui/data/_time_series_query`. The request and response bodies are specifed in [`lib/core_query_types.ts`][it-core-query] and @@ -93,7 +93,7 @@ for the last 10 seconds. This example uses [now-iso][] to generate iso date strings. ```console -curl -k "https://elastic:changeme@localhost:5601/api/triggers_actions_ui/data/_time_series_query" \ +curl -k "https://elastic:changeme@localhost:5601/internal/triggers_actions_ui/data/_time_series_query" \ -H "kbn-xsrf: foo" -H "content-type: application/json" -d "{ \"index\": \"es-hb-sim\", \"timeField\": \"@timestamp\", @@ -136,7 +136,7 @@ curl -k "https://elastic:changeme@localhost:5601/api/triggers_actions_ui/data/_ To get the current value of the calculated metric, you can leave off the date: ``` -curl -k "https://elastic:changeme@localhost:5601/api/triggers_actions_ui/data/_time_series_query" \ +curl -k "https://elastic:changeme@localhost:5601/internal/triggers_actions_ui/data/_time_series_query" \ -H "kbn-xsrf: foo" -H "content-type: application/json" -d '{ "index": "es-hb-sim", "timeField": "@timestamp", @@ -169,7 +169,7 @@ curl -k "https://elastic:changeme@localhost:5601/api/triggers_actions_ui/data/_ ## service functions A single service function is available that provides the functionality -of the http endpoint `POST /api/triggers_actions_ui/data/_time_series_query`, +of the http endpoint `POST /internal/triggers_actions_ui/data/_time_series_query`, but as an API for Kibana plugins. The function is available as `triggersActionsUi.data.timeSeriesQuery()` on the plugin's _Start_ contract diff --git a/x-pack/plugins/triggers_actions_ui/server/routes/config.test.ts b/x-pack/plugins/triggers_actions_ui/server/routes/config.test.ts index 79b5f6f85d4e..9c32256f91bc 100644 --- a/x-pack/plugins/triggers_actions_ui/server/routes/config.test.ts +++ b/x-pack/plugins/triggers_actions_ui/server/routes/config.test.ts @@ -12,13 +12,13 @@ describe('createConfigRoute', () => { it('registers the route', async () => { const router = httpServiceMock.createRouter(); const logger = loggingSystemMock.create().get(); - createConfigRoute(logger, router, `/api/triggers_actions_ui`, () => ({ + createConfigRoute(logger, router, `/internal/triggers_actions_ui`, () => ({ isUsingSecurity: true, minimumScheduleInterval: { value: '1m', enforce: false }, })); const [config, handler] = router.get.mock.calls[0]; - expect(config.path).toMatchInlineSnapshot(`"/api/triggers_actions_ui/_config"`); + expect(config.path).toMatchInlineSnapshot(`"/internal/triggers_actions_ui/_config"`); const mockResponse = httpServerMock.createResponseFactory(); await handler({}, httpServerMock.createKibanaRequest(), mockResponse); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/disable.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/disable.ts index 2f452a54927b..860576f806e3 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/disable.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/disable.ts @@ -26,7 +26,8 @@ export default function createDisableAlertTests({ getService }: FtrProviderConte const supertest = getService('supertest'); const supertestWithoutAuth = getService('supertestWithoutAuth'); - describe('disable', () => { + // Failing: See https://github.com/elastic/kibana/issues/140797 + describe.skip('disable', () => { const objectRemover = new ObjectRemover(supertest); after(() => objectRemover.removeAll()); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/fields_endpoint.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/fields_endpoint.ts index 0a48e206e020..37e71ac0e1a1 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/fields_endpoint.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/fields_endpoint.ts @@ -11,7 +11,7 @@ import { Spaces } from '../../../../scenarios'; import { FtrProviderContext } from '../../../../../common/ftr_provider_context'; import { ESTestIndexTool, ES_TEST_INDEX_NAME, getUrlPrefix } from '../../../../../common/lib'; -const API_URI = 'api/triggers_actions_ui/data/_fields'; +const API_URI = 'internal/triggers_actions_ui/data/_fields'; // eslint-disable-next-line import/no-default-export export default function fieldsEndpointTests({ getService }: FtrProviderContext) { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/indices_endpoint.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/indices_endpoint.ts index 894dd5848ee7..fb70420d5f45 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/indices_endpoint.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/indices_endpoint.ts @@ -13,7 +13,7 @@ import { ESTestIndexTool, ES_TEST_INDEX_NAME, getUrlPrefix } from '../../../../. import { createEsDocumentsWithGroups } from '../lib/create_test_data'; import { createDataStream, deleteDataStream } from '../lib/create_test_data'; -const API_URI = 'api/triggers_actions_ui/data/_indices'; +const API_URI = 'internal/triggers_actions_ui/data/_indices'; // eslint-disable-next-line import/no-default-export export default function indicesEndpointTests({ getService }: FtrProviderContext) { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/time_series_query_endpoint.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/time_series_query_endpoint.ts index 52fae5766e86..5eee05a916da 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/time_series_query_endpoint.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/time_series_query_endpoint.ts @@ -14,7 +14,8 @@ import { ESTestIndexTool, ES_TEST_INDEX_NAME, getUrlPrefix } from '../../../../. import { createEsDocumentsWithGroups } from '../lib/create_test_data'; -const INDEX_THRESHOLD_TIME_SERIES_QUERY_URL = 'api/triggers_actions_ui/data/_time_series_query'; +const INDEX_THRESHOLD_TIME_SERIES_QUERY_URL = + 'internal/triggers_actions_ui/data/_time_series_query'; const START_DATE_MM_DD_HH_MM_SS_MS = '01-01T00:00:00.000Z'; const START_DATE = `2020-${START_DATE_MM_DD_HH_MM_SS_MS}`; diff --git a/x-pack/test/api_integration/apis/uptime/rest/edit_monitor.ts b/x-pack/test/api_integration/apis/uptime/rest/edit_monitor.ts index 7bcda35a892f..480d07e7144f 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/edit_monitor.ts +++ b/x-pack/test/api_integration/apis/uptime/rest/edit_monitor.ts @@ -16,8 +16,7 @@ import { getFixtureJson } from './helper/get_fixture_json'; import { PrivateLocationTestService } from './services/private_location_test_service'; export default function ({ getService }: FtrProviderContext) { - // Failing: See https://github.com/elastic/kibana/issues/140520 - describe.skip('[PUT] /internal/uptime/service/monitors', function () { + describe('[PUT] /internal/uptime/service/monitors', function () { this.tags('skipCloud'); const supertest = getService('supertest'); diff --git a/x-pack/test/apm_api_integration/common/apm_api_supertest.ts b/x-pack/test/apm_api_integration/common/apm_api_supertest.ts index 86beb73d51ea..c492f7af04a6 100644 --- a/x-pack/test/apm_api_integration/common/apm_api_supertest.ts +++ b/x-pack/test/apm_api_integration/common/apm_api_supertest.ts @@ -41,10 +41,19 @@ export function createApmApiClient(st: supertest.SuperTest) { }; } +type ApiErrorResponse = Omit & { + body: { + statusCode: number; + error: string; + message: string; + attributes: object; + }; +}; + export type ApmApiSupertest = ReturnType; export class ApmApiError extends Error { - res: request.Response; + res: ApiErrorResponse; constructor(res: request.Response, endpoint: string) { super( diff --git a/x-pack/test/apm_api_integration/tests/data_view/static.spec.ts b/x-pack/test/apm_api_integration/tests/data_view/static.spec.ts index 11bb01d3ca7b..0ed65724bfd4 100644 --- a/x-pack/test/apm_api_integration/tests/data_view/static.spec.ts +++ b/x-pack/test/apm_api_integration/tests/data_view/static.spec.ts @@ -8,30 +8,37 @@ import { apm, ApmSynthtraceEsClient, timerange } from '@kbn/apm-synthtrace'; import expect from '@kbn/expect'; import { APM_STATIC_DATA_VIEW_ID } from '@kbn/apm-plugin/common/data_view_constants'; +import { DataView } from '@kbn/data-views-plugin/common'; +import request from 'superagent'; import { FtrProviderContext } from '../../common/ftr_provider_context'; -import { SupertestReturnType } from '../../common/apm_api_supertest'; +import { SupertestReturnType, ApmApiError } from '../../common/apm_api_supertest'; export default function ApiTest({ getService }: FtrProviderContext) { const registry = getService('registry'); const apmApiClient = getService('apmApiClient'); const supertest = getService('supertest'); const synthtrace = getService('synthtraceEsClient'); - const dataViewPattern = 'traces-apm*,apm-*,logs-apm*,apm-*,metrics-apm*,apm-*'; - function createDataViewViaApmApi() { + function createDataViewWithWriteUser() { return apmApiClient.writeUser({ endpoint: 'POST /internal/apm/data_view/static' }); } + function createDataViewWithReadUser() { + return apmApiClient.readUser({ endpoint: 'POST /internal/apm/data_view/static' }); + } + function deleteDataView() { return supertest - .delete(`/api/saved_objects/index-pattern/${APM_STATIC_DATA_VIEW_ID}`) - .set('kbn-xsrf', 'foo') - .expect(200); + .delete(`/api/saved_objects/index-pattern/${APM_STATIC_DATA_VIEW_ID}?force=true`) + .set('kbn-xsrf', 'foo'); } - function getDataView() { - return supertest.get(`/api/saved_objects/index-pattern/${APM_STATIC_DATA_VIEW_ID}`); + function getDataView({ space }: { space: string }) { + const spacePrefix = space !== 'default' ? `/s/${space}` : ''; + return supertest.get( + `${spacePrefix}/api/saved_objects/index-pattern/${APM_STATIC_DATA_VIEW_ID}` + ); } function getDataViewSuggestions(field: string) { @@ -43,90 +50,150 @@ export default function ApiTest({ getService }: FtrProviderContext) { registry.when('no mappings exist', { config: 'basic', archives: [] }, () => { let response: SupertestReturnType<'POST /internal/apm/data_view/static'>; - describe('when no data is generated', () => { - before(async () => { - response = await createDataViewViaApmApi(); - }); + before(async () => { + response = await createDataViewWithWriteUser(); + }); - it('does not create data view', async () => { - expect(response.status).to.be(200); - expect(response.body.dataView).to.be(undefined); + it('does not create data view', async () => { + expect(response.status).to.be(200); + expect(response.body).to.eql({ + created: false, + reason: 'No APM data', }); + }); - it('cannot fetch data view', async () => { - await getDataView().expect(404); - }); + it('cannot fetch data view', async () => { + const res = await getDataView({ space: 'default' }); + expect(res.status).to.be(404); + expect(res.body.message).to.eql( + 'Saved object [index-pattern/apm_static_index_pattern_id] not found' + ); }); }); - registry.when('mappings exists', { config: 'basic', archives: [] }, () => { - describe('when data is generated', () => { + registry.when('mappings and APM data exists', { config: 'basic', archives: [] }, () => { + before(async () => { + await generateApmData(synthtrace); + }); + + after(async () => { + await synthtrace.clean(); + }); + + afterEach(async () => { + await deleteDataView(); + }); + + describe('when creating data view with write user', () => { let response: SupertestReturnType<'POST /internal/apm/data_view/static'>; before(async () => { - await generateApmData(synthtrace); - response = await createDataViewViaApmApi(); - }); - - after(async () => { - await deleteDataView(); - await synthtrace.clean(); + response = await createDataViewWithWriteUser(); }); it('successfully creates the apm data view', async () => { expect(response.status).to.be(200); - expect(response.body.dataView!.id).to.be('apm_static_index_pattern_id'); - expect(response.body.dataView!.name).to.be('APM'); - expect(response.body.dataView!.title).to.be( - 'traces-apm*,apm-*,logs-apm*,apm-*,metrics-apm*,apm-*' - ); + // @ts-expect-error + const dataView = response.body.dataView as DataView; + + expect(dataView.id).to.be('apm_static_index_pattern_id'); + expect(dataView.name).to.be('APM'); + expect(dataView.title).to.be('traces-apm*,apm-*,logs-apm*,apm-*,metrics-apm*,apm-*'); }); + }); - describe('when fetching the data view', async () => { - let resBody: any; + describe('when fetching the data view', async () => { + let dataViewResponse: request.Response; - before(async () => { - const res = await getDataView().expect(200); - resBody = res.body; - }); + before(async () => { + await createDataViewWithWriteUser(); + dataViewResponse = await getDataView({ space: 'default' }); + }); - it('has correct id', () => { - expect(resBody.id).to.be('apm_static_index_pattern_id'); - }); + it('return 200', () => { + expect(dataViewResponse.status).to.be(200); + }); - it('has correct title', () => { - expect(resBody.attributes.title).to.be(dataViewPattern); - }); + it('has correct id', () => { + expect(dataViewResponse.body.id).to.be('apm_static_index_pattern_id'); + }); - it('has correct attributes', () => { - expect(resBody.attributes.fieldFormatMap).to.be( - JSON.stringify({ - 'trace.id': { - id: 'url', - params: { - urlTemplate: 'apm/link-to/trace/{{value}}', - labelTemplate: '{{value}}', - }, + it('has correct title', () => { + expect(dataViewResponse.body.attributes.title).to.be(dataViewPattern); + }); + + it('has correct attributes', () => { + expect(dataViewResponse.body.attributes.fieldFormatMap).to.be( + JSON.stringify({ + 'trace.id': { + id: 'url', + params: { + urlTemplate: 'apm/link-to/trace/{{value}}', + labelTemplate: '{{value}}', }, - 'transaction.id': { - id: 'url', - params: { - urlTemplate: 'apm/link-to/transaction/{{value}}', - labelTemplate: '{{value}}', - }, + }, + 'transaction.id': { + id: 'url', + params: { + urlTemplate: 'apm/link-to/transaction/{{value}}', + labelTemplate: '{{value}}', }, - }) - ); - }); + }, + }) + ); + }); + + // this test ensures that the default APM Data View doesn't interfere with suggestions returned in the kuery bar (this has been a problem in the past) + it('can get suggestions for `trace.id`', async () => { + const suggestions = await getDataViewSuggestions('trace.id'); + expect(suggestions.body.length).to.be(10); + }); + }); + + describe('when creating data view via read user', () => { + it('throws an error', async () => { + try { + await createDataViewWithReadUser(); + } catch (e) { + const err = e as ApmApiError; + const responseBody = err.res.body; + expect(err.res.status).to.eql(403); + expect(responseBody.statusCode).to.eql(403); + expect(responseBody.error).to.eql('Forbidden'); + expect(responseBody.message).to.eql('Unable to create index-pattern'); + } + }); + }); + + describe('when creating data view twice', () => { + it('returns 200 response with reason, if data view already exists', async () => { + await createDataViewWithWriteUser(); + const res = await createDataViewWithWriteUser(); - // this test ensures that the default APM Data View doesn't interfere with suggestions returned in the kuery bar (this has been a problem in the past) - it('can get suggestions for `trace.id`', async () => { - const suggestions = await getDataViewSuggestions('trace.id'); - expect(suggestions.body.length).to.be(10); + expect(res.status).to.be(200); + expect(res.body).to.eql({ + created: false, + reason: 'Dataview already exists in the active space', }); }); }); + + describe('when creating data view in "default" space', async () => { + it('can be retrieved from the "default space"', async () => { + await createDataViewWithWriteUser(); + const res = await getDataView({ space: 'default' }); + expect(res.body.id).to.eql('apm_static_index_pattern_id'); + expect(res.body.namespaces).to.eql(['*', 'default']); + }); + + it('can be retrieved from the "foo" space', async () => { + await createDataViewWithWriteUser(); + const res = await getDataView({ space: 'foo' }); + expect(res.body.id).to.eql('apm_static_index_pattern_id'); + expect(res.body.namespaces).to.eql(['*', 'default']); + }); + }); }); } @@ -137,7 +204,7 @@ function generateApmData(synthtrace: ApmSynthtraceEsClient) { ); const instance = apm - .service({ name: 'multiple-env-service', environment: 'production', agentName: 'go' }) + .service({ name: 'my-service', environment: 'production', agentName: 'go' }) .instance('my-instance'); return synthtrace.index([ diff --git a/x-pack/test/apm_api_integration/tests/index.ts b/x-pack/test/apm_api_integration/tests/index.ts index 9f882797b0a1..53c685a07e8b 100644 --- a/x-pack/test/apm_api_integration/tests/index.ts +++ b/x-pack/test/apm_api_integration/tests/index.ts @@ -16,7 +16,7 @@ function getGlobPattern() { return '**/*.spec.ts'; } - return envGrepFiles.includes('.spec.ts') ? envGrepFiles : `**/*${envGrepFiles}*.spec.ts`; + return envGrepFiles.includes('**') ? envGrepFiles : `**/*${envGrepFiles}*`; } export default function apmApiIntegrationTests({ getService, loadTestFile }: FtrProviderContext) { diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/find_cases.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/find_cases.ts index df0fdaeba697..3c076d4e3514 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/find_cases.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/find_cases.ts @@ -55,8 +55,7 @@ export default ({ getService }: FtrProviderContext): void => { const esArchiver = getService('esArchiver'); const kibanaServer = getService('kibanaServer'); - // Failing: See https://github.com/elastic/kibana/issues/139626 - describe.skip('find_cases', () => { + describe('find_cases', () => { describe('basic tests', () => { afterEach(async () => { await deleteAllCaseItems(es); @@ -369,6 +368,11 @@ export default ({ getService }: FtrProviderContext): void => { owner: 'securitySolutionFixture', }, }); + + // There is potential for the alert index to not be refreshed by the time the second comment is created + // which could attempt to update the alert status again and will encounter a conflict so this will + // ensure that the index is up to date before we try to update the next alert status + await es.indices.refresh({ index: defaultSignalsIndex }); } const patchedCase = await createComment({ diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors.ts index 78287b57a584..0abb9bd7a395 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors.ts @@ -6,6 +6,7 @@ */ import expect from '@kbn/expect'; +import { findIndex } from 'lodash'; import { FtrProviderContext } from '../../ftr_provider_context'; import { ObjectRemover } from '../../lib/object_remover'; import { generateUniqueKey, getTestActionData } from '../../lib/get_test_data'; @@ -15,13 +16,10 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const pageObjects = getPageObjects(['common', 'triggersActionsUI', 'header']); const find = getService('find'); const retry = getService('retry'); - const comboBox = getService('comboBox'); const supertest = getService('supertest'); + const objectRemover = new ObjectRemover(supertest); - // FLAKY: https://github.com/elastic/kibana/issues/88796 - describe.skip('Connectors', function () { - const objectRemover = new ObjectRemover(supertest); - + describe('Connectors', function () { before(async () => { const { body: createdAction } = await supertest .post(`/api/actions/connector`) @@ -44,7 +42,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await testSubjects.click('.index-card'); - await find.clickByCssSelector('[data-test-subj="backButton"]'); + await find.clickByCssSelector('[data-test-subj="create-connector-flyout-back-btn"]'); await testSubjects.click('.slack-card'); @@ -68,12 +66,15 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { actionType: 'Slack', }, ]); + const connector = await getConnector(connectorName); + objectRemover.add(connector.id, 'action', 'actions'); }); it('should edit a connector', async () => { const connectorName = generateUniqueKey(); const updatedConnectorName = `${connectorName}updated`; - await createConnector(connectorName); + const createdAction = await createConnector(connectorName); + objectRemover.add(createdAction.id, 'action', 'actions'); await pageObjects.triggersActionsUI.searchConnectors(connectorName); @@ -107,7 +108,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { it('should test a connector and display a successful result', async () => { const connectorName = generateUniqueKey(); const indexName = generateUniqueKey(); - await createIndexConnector(connectorName, indexName); + const createdAction = await createIndexConnector(connectorName, indexName); + objectRemover.add(createdAction.id, 'action', 'actions'); await pageObjects.triggersActionsUI.searchConnectors(connectorName); @@ -119,7 +121,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await find.clickByCssSelector('[data-test-subj="testConnectorTab"]'); // test success - await testSubjects.setValue('documentsJsonEditor', '{ "key": "value" }'); + await find.setValueByClass('kibanaCodeEditor', '{ "key": "value" }'); await find.clickByCssSelector('[data-test-subj="executeActionButton"]:not(disabled)'); @@ -128,14 +130,15 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); await find.clickByCssSelector( - '[data-test-subj="cancelSaveEditedConnectorButton"]:not(disabled)' + '[data-test-subj="edit-connector-flyout-cancel-btn"]:not(disabled)' ); }); it('should test a connector and display a failure result', async () => { const connectorName = generateUniqueKey(); const indexName = generateUniqueKey(); - await createIndexConnector(connectorName, indexName); + const createdAction = await createIndexConnector(connectorName, indexName); + objectRemover.add(createdAction.id, 'action', 'actions'); await pageObjects.triggersActionsUI.searchConnectors(connectorName); @@ -146,25 +149,23 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await find.clickByCssSelector('[data-test-subj="testConnectorTab"]'); - await testSubjects.setValue('documentsJsonEditor', '{ "": "value" }'); + await find.setValueByClass('kibanaCodeEditor', '"test"'); await find.clickByCssSelector('[data-test-subj="executeActionButton"]:not(disabled)'); await retry.try(async () => { - const executionFailureResultCallout = await testSubjects.find('executionFailureResult'); - expect(await executionFailureResultCallout.getVisibleText()).to.match( - /error indexing documents/ - ); + await testSubjects.find('executionFailureResult'); }); await find.clickByCssSelector( - '[data-test-subj="cancelSaveEditedConnectorButton"]:not(disabled)' + '[data-test-subj="edit-connector-flyout-cancel-btn"]:not(disabled)' ); }); it('should reset connector when canceling an edit', async () => { const connectorName = generateUniqueKey(); - await createConnector(connectorName); + const createdAction = await createConnector(connectorName); + objectRemover.add(createdAction.id, 'action', 'actions'); await pageObjects.triggersActionsUI.searchConnectors(connectorName); const searchResultsBeforeEdit = await pageObjects.triggersActionsUI.getConnectorsList(); @@ -173,9 +174,9 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await find.clickByCssSelector('[data-test-subj="connectorsTableCell-name"] button'); await testSubjects.setValue('nameInput', 'some test name to cancel'); - await testSubjects.click('cancelSaveEditedConnectorButton'); + await testSubjects.click('edit-connector-flyout-cancel-btn'); - await find.waitForDeletedByCssSelector('[data-test-subj="cancelSaveEditedConnectorButton"]'); + await find.waitForDeletedByCssSelector('[data-test-subj="edit-connector-flyout-cancel-btn"]'); await pageObjects.triggersActionsUI.searchConnectors(connectorName); @@ -189,8 +190,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { it('should delete a connector', async () => { const connectorName = generateUniqueKey(); await createConnector(connectorName); - - await createConnector(generateUniqueKey()); + const createdAction = await createConnector(generateUniqueKey()); + objectRemover.add(createdAction.id, 'action', 'actions'); await pageObjects.triggersActionsUI.searchConnectors(connectorName); @@ -214,8 +215,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { it('should bulk delete connectors', async () => { const connectorName = generateUniqueKey(); await createConnector(connectorName); - - await createConnector(generateUniqueKey()); + const createdAction = await createConnector(generateUniqueKey()); + objectRemover.add(createdAction.id, 'action', 'actions'); await pageObjects.triggersActionsUI.searchConnectors(connectorName); @@ -269,42 +270,46 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); async function createConnector(connectorName: string) { - await pageObjects.triggersActionsUI.clickCreateConnectorButton(); - - await testSubjects.click('.slack-card'); - - await testSubjects.setValue('nameInput', connectorName); - - await testSubjects.setValue('slackWebhookUrlInput', 'https://test.com'); - - await find.clickByCssSelector( - '[data-test-subj="create-connector-flyout-save-btn"]:not(disabled)' - ); - await pageObjects.common.closeToast(); + const { body: createdAction } = await supertest + .post(`/api/actions/connector`) + .set('kbn-xsrf', 'foo') + .send({ + name: connectorName, + config: {}, + secrets: { + webhookUrl: 'https://test.com', + }, + connector_type_id: '.slack', + }) + .expect(200); + await testSubjects.click('connectorsTab'); + return createdAction; } async function createIndexConnector(connectorName: string, indexName: string) { - await pageObjects.triggersActionsUI.clickCreateConnectorButton(); - - await testSubjects.click('.index-card'); - - await testSubjects.setValue('nameInput', connectorName); - - await retry.try(async () => { - // At times we find the driver controlling the ComboBox in tests - // can select the wrong item, this ensures we always select the correct index - await comboBox.set('connectorIndexesComboBox', indexName); - expect( - await comboBox.isOptionSelected( - await testSubjects.find('connectorIndexesComboBox'), - indexName - ) - ).to.be(true); - }); + const { body: createdAction } = await supertest + .post(`/api/actions/connector`) + .set('kbn-xsrf', 'foo') + .send({ + config: { + index: indexName, + refresh: false, + }, + connector_type_id: '.index', + name: connectorName, + secrets: {}, + }) + .expect(200); + await testSubjects.click('connectorsTab'); + return createdAction; + } - await find.clickByCssSelector( - '[data-test-subj="create-connector-flyout-save-btn"]:not(disabled)' - ); - await pageObjects.common.closeToast(); + async function getConnector(name: string) { + const { body } = await supertest + .get(`/api/actions/connectors`) + .set('kbn-xsrf', 'foo') + .expect(200); + const i = findIndex(body, (c: any) => c.name === name); + return body[i]; } }; diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts index 12ea3e745250..443170a43215 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts @@ -25,7 +25,8 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const policyTestResources = getService('policyTestResources'); const endpointTestResources = getService('endpointTestResources'); - describe('When on the Endpoint Policy Details Page', function () { + // Failing: See https://github.com/elastic/kibana/issues/138776 + describe.skip('When on the Endpoint Policy Details Page', function () { let indexedData: IndexedHostsAndAlertsResponse; before(async () => { diff --git a/yarn.lock b/yarn.lock index 535bf43b01e1..d172285b2080 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3040,6 +3040,18 @@ version "0.0.0" uid "" +"@kbn/core-lifecycle-browser-internal@link:bazel-bin/packages/core/lifecycle/core-lifecycle-browser-internal": + version "0.0.0" + uid "" + +"@kbn/core-lifecycle-browser-mocks@link:bazel-bin/packages/core/lifecycle/core-lifecycle-browser-mocks": + version "0.0.0" + uid "" + +"@kbn/core-lifecycle-browser@link:bazel-bin/packages/core/lifecycle/core-lifecycle-browser": + version "0.0.0" + uid "" + "@kbn/core-logging-server-internal@link:bazel-bin/packages/core/logging/core-logging-server-internal": version "0.0.0" uid "" @@ -3116,6 +3128,18 @@ version "0.0.0" uid "" +"@kbn/core-plugins-browser-internal@link:bazel-bin/packages/core/plugins/core-plugins-browser-internal": + version "0.0.0" + uid "" + +"@kbn/core-plugins-browser-mocks@link:bazel-bin/packages/core/plugins/core-plugins-browser-mocks": + version "0.0.0" + uid "" + +"@kbn/core-plugins-browser@link:bazel-bin/packages/core/plugins/core-plugins-browser": + version "0.0.0" + uid "" + "@kbn/core-preboot-server-internal@link:bazel-bin/packages/core/preboot/core-preboot-server-internal": version "0.0.0" uid "" @@ -7093,6 +7117,18 @@ version "0.0.0" uid "" +"@types/kbn__core-lifecycle-browser-internal@link:bazel-bin/packages/core/lifecycle/core-lifecycle-browser-internal/npm_module_types": + version "0.0.0" + uid "" + +"@types/kbn__core-lifecycle-browser-mocks@link:bazel-bin/packages/core/lifecycle/core-lifecycle-browser-mocks/npm_module_types": + version "0.0.0" + uid "" + +"@types/kbn__core-lifecycle-browser@link:bazel-bin/packages/core/lifecycle/core-lifecycle-browser/npm_module_types": + version "0.0.0" + uid "" + "@types/kbn__core-logging-server-internal@link:bazel-bin/packages/core/logging/core-logging-server-internal/npm_module_types": version "0.0.0" uid "" @@ -7169,6 +7205,18 @@ version "0.0.0" uid "" +"@types/kbn__core-plugins-browser-internal@link:bazel-bin/packages/core/plugins/core-plugins-browser-internal/npm_module_types": + version "0.0.0" + uid "" + +"@types/kbn__core-plugins-browser-mocks@link:bazel-bin/packages/core/plugins/core-plugins-browser-mocks/npm_module_types": + version "0.0.0" + uid "" + +"@types/kbn__core-plugins-browser@link:bazel-bin/packages/core/plugins/core-plugins-browser/npm_module_types": + version "0.0.0" + uid "" + "@types/kbn__core-preboot-server-internal@link:bazel-bin/packages/core/preboot/core-preboot-server-internal/npm_module_types": version "0.0.0" uid ""