diff --git a/.bazelignore b/.bazelignore index ac7a2d15a7aa4..ec61c2f3e7e75 100644 --- a/.bazelignore +++ b/.bazelignore @@ -8,7 +8,10 @@ .idea .teamcity .yarn-local-mirror -/bazel +bazel-bin +bazel-kibana +bazel-out +bazel-testlogs build node_modules target diff --git a/.bazelrc.common b/.bazelrc.common index fb8e8e86b9ef5..20a41c4cde9a0 100644 --- a/.bazelrc.common +++ b/.bazelrc.common @@ -18,17 +18,16 @@ build --disk_cache=~/.bazel-cache/disk-cache build --repository_cache=~/.bazel-cache/repository-cache # Bazel will create symlinks from the workspace directory to output artifacts. -# Build results will be placed in a directory called "bazel/bin" +# Build results will be placed in a directory called "bazel-bin" # This will still create a bazel-out symlink in # the project directory, which must be excluded from the # editor's search path. -build --symlink_prefix=bazel/ # To disable the symlinks altogether (including bazel-out) we can use # build --symlink_prefix=/ # however this makes it harder to find outputs. # Prevents the creation of bazel-out dir -build --experimental_no_product_name_out_symlink +# build --experimental_no_product_name_out_symlink # Make direct file system calls to create symlink trees build --experimental_inprocess_symlink_creation @@ -83,7 +82,7 @@ test:debug --test_output=streamed --test_strategy=exclusive --test_timeout=9999 run:debug --define=VERBOSE_LOGS=1 -- --node_options=--inspect-brk # The following option will change the build output of certain rules such as terser and may not be desirable in all cases # It will also output both the repo cache and action cache to a folder inside the repo -build:debug --compilation_mode=dbg --show_result=1 --disk_cache=bazel/disk-cache --repository_cache=bazel/repository-cache +build:debug --compilation_mode=dbg --show_result=1 # Turn off legacy external runfiles # This prevents accidentally depending on this feature, which Bazel will remove. diff --git a/.eslintignore b/.eslintignore index 4559711bb9dd3..bbd8e3f88a378 100644 --- a/.eslintignore +++ b/.eslintignore @@ -48,4 +48,4 @@ snapshots.js /packages/kbn-monaco/src/painless/antlr # Bazel -/bazel +/bazel-* diff --git a/.gitignore b/.gitignore index 288bdd2e53444..3859fd754c528 100644 --- a/.gitignore +++ b/.gitignore @@ -78,5 +78,5 @@ report.asciidoc .yarn-local-mirror # Bazel -/bazel -/.bazelrc.user +bazel-* +.bazelrc.user diff --git a/.stylelintignore b/.stylelintignore index a48b3adfa3632..72d9d5104a0e9 100644 --- a/.stylelintignore +++ b/.stylelintignore @@ -1,3 +1,4 @@ x-pack/plugins/canvas/shareable_runtime/**/*.s+(a|c)ss build target +bazel-* diff --git a/BUILD.bazel b/BUILD.bazel index 38a478565a4af..4502daeaacb59 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -2,6 +2,7 @@ # other packages builds and need to be included as inputs exports_files( [ + "tsconfig.base.json", "tsconfig.json", "package.json" ], diff --git a/docs/discover/search.asciidoc b/docs/discover/search.asciidoc index 9971a6f574f9c..0306be3eb670d 100644 --- a/docs/discover/search.asciidoc +++ b/docs/discover/search.asciidoc @@ -110,7 +110,7 @@ image::discover/images/read-only-badge.png[Example of Discover's read only acces ==== Save a search To save the current search: -. Click *Save* in the Kibana toolbar. +. Click *Save* in the toolbar. . Enter a name for the search and click *Save*. To import, export, and delete saved searches, open the main menu, @@ -119,7 +119,7 @@ then click *Stack Management > Saved Objects*. ==== Open a saved search To load a saved search into Discover: -. Click *Open* in the Kibana toolbar. +. Click *Open* in the toolbar. . Select the search you want to open. If the saved search is associated with a different index pattern than is currently diff --git a/docs/maps/maps-aggregations.asciidoc b/docs/maps/maps-aggregations.asciidoc index 265bf6bfaea30..7f4af952653e7 100644 --- a/docs/maps/maps-aggregations.asciidoc +++ b/docs/maps/maps-aggregations.asciidoc @@ -76,9 +76,8 @@ then accumulates the most relevant documents based on sort order for each entry To enable top hits: -. Click *Add layer*, then select the *Documents* layer. +. Click *Add layer*, then select the *Top hits per entity* layer. . Configure *Index pattern* and *Geospatial field*. -. In *Scaling*, select *Show top hits per entity*. . Set *Entity* to the field that identifies entities in your documents. This field will be used in the terms aggregation to group your documents into entity buckets. . Set *Documents per entity* to configure the maximum number of documents accumulated per entity. diff --git a/docs/maps/vector-layer.asciidoc b/docs/maps/vector-layer.asciidoc index 6a2228161845e..2115c16a889c6 100644 --- a/docs/maps/vector-layer.asciidoc +++ b/docs/maps/vector-layer.asciidoc @@ -23,8 +23,6 @@ Select the appropriate *Scaling* option for your use case. * *Limit results to 10000.* The layer displays features from the first `index.max_result_window` documents. Results exceeding `index.max_result_window` are not displayed. -* *Show top hits per entity.* The layer displays the <>. - * *Show clusters when results exceed 10000.* When results exceed `index.max_result_window`, the layer uses {ref}/search-aggregations-bucket-geotilegrid-aggregation.html[GeoTile grid aggregation] to group your documents into clusters and displays metrics for each cluster. When results are less then `index.max_result_window`, the layer displays features from individual documents. * *Use vector tiles.* Vector tiles partition your map into 6 to 8 tiles. @@ -36,6 +34,9 @@ Tiles exceeding `index.max_result_window` have a visual indicator when there are *Point to point*:: Aggregated data paths between the source and destination. The index must contain at least 2 fields mapped as {ref}/geo-point.html[geo_point], source and destination. +*Top hits per entity*:: The layer displays the <>. +The index must contain at least one field mapped as {ref}/geo-point.html[geo_point] or {ref}/geo-shape.html[geo_shape]. + *Tracks*:: Create lines from points. The index must contain at least one field mapped as {ref}/geo-point.html[geo_point]. diff --git a/package.json b/package.json index 5c91fbe561944..fc2f82039fa78 100644 --- a/package.json +++ b/package.json @@ -95,7 +95,7 @@ "@elastic/apm-rum": "^5.6.1", "@elastic/apm-rum-react": "^1.2.5", "@elastic/charts": "26.0.0", - "@elastic/datemath": "link:packages/elastic-datemath", + "@elastic/datemath": "link:bazel-bin/packages/elastic-datemath/npm_module", "@elastic/elasticsearch": "npm:@elastic/elasticsearch-canary@7.13.0-canary.1", "@elastic/ems-client": "7.12.0", "@elastic/eui": "31.10.0", @@ -438,6 +438,7 @@ "@babel/traverse": "^7.12.12", "@babel/types": "^7.12.12", "@bazel/ibazel": "^0.14.0", + "@bazel/typescript": "^3.2.3", "@cypress/snapshot": "^2.1.7", "@cypress/webpack-preprocessor": "^5.5.0", "@elastic/apm-rum": "^5.6.1", diff --git a/packages/BUILD.bazel b/packages/BUILD.bazel index 1f1eba0747ab7..31894fcb1bb5d 100644 --- a/packages/BUILD.bazel +++ b/packages/BUILD.bazel @@ -2,5 +2,7 @@ # targets so we can build them all at once filegroup( name = "build", - srcs = [], + srcs = [ + "//packages/elastic-datemath:build" + ], ) diff --git a/packages/elastic-datemath/.npmignore b/packages/elastic-datemath/.npmignore index 591be7afd1669..cb8c40d17ea04 100644 --- a/packages/elastic-datemath/.npmignore +++ b/packages/elastic-datemath/.npmignore @@ -1,2 +1,3 @@ +/index.test.js +/jest.config.js /tsconfig.json -/__tests__ diff --git a/packages/elastic-datemath/BUILD.bazel b/packages/elastic-datemath/BUILD.bazel new file mode 100644 index 0000000000000..6a80556d4eed5 --- /dev/null +++ b/packages/elastic-datemath/BUILD.bazel @@ -0,0 +1,76 @@ +load("@npm//@bazel/typescript:index.bzl", "ts_config", "ts_project") +load("@build_bazel_rules_nodejs//:index.bzl", "js_library", "pkg_npm") + +PKG_BASE_NAME = "elastic-datemath" +PKG_REQUIRE_NAME = "@elastic/datemath" + +SOURCE_FILES = [ + "src/index.ts", +] + +SRCS = SOURCE_FILES + +filegroup( + name = "srcs", + srcs = glob(SOURCE_FILES), +) + +NPM_MODULE_EXTRA_FILES = [ + "package.json", + "README.md", +] + +SRC_DEPS = [ + "@npm//moment", +] + +TYPES_DEPS = [ + "@npm//@types/node", +] + +DEPS = SRC_DEPS + TYPES_DEPS + +ts_config( + name = "tsconfig", + src = "tsconfig.json", + deps = [ + "//:tsconfig.base.json", + ], +) + +ts_project( + name = "tsc", + srcs = SRCS, + deps = DEPS, + declaration = True, + declaration_map = True, + incremental = True, + out_dir = "target", + source_map = True, + root_dir = "src", + tsconfig = ":tsconfig", +) + +js_library( + name = PKG_BASE_NAME, + srcs = [], + deps = [":tsc"] + DEPS, + package_name = PKG_REQUIRE_NAME, + visibility = ["//visibility:public"], +) + +pkg_npm( + name = "npm_module", + srcs = NPM_MODULE_EXTRA_FILES, + deps = [ + ":%s" % PKG_BASE_NAME, + ] +) + +filegroup( + name = "build", + srcs = [ + ":npm_module", + ], + visibility = ["//visibility:public"], +) diff --git a/packages/elastic-datemath/package.json b/packages/elastic-datemath/package.json index 4dc9c4f24d567..67fbb74eb223c 100644 --- a/packages/elastic-datemath/package.json +++ b/packages/elastic-datemath/package.json @@ -5,8 +5,7 @@ "license": "Apache-2.0", "main": "./target/index.js", "types": "./target/index.d.ts", - "scripts": { - "build": "../../node_modules/.bin/tsc", - "kbn:bootstrap": "yarn build" + "peerDependencies": { + "moment": "^2.24.0" } } \ No newline at end of file diff --git a/packages/elastic-datemath/tsconfig.json b/packages/elastic-datemath/tsconfig.json index 6f04bee983a9e..d0fa806ed411b 100644 --- a/packages/elastic-datemath/tsconfig.json +++ b/packages/elastic-datemath/tsconfig.json @@ -1,10 +1,10 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { - "incremental": false, - "outDir": "./target", "declaration": true, "declarationMap": true, + "outDir": "target", + "rootDir": "src", "sourceMap": true, "sourceRoot": "../../../../packages/elastic-datemath/src", "types": [ diff --git a/packages/kbn-cli-dev-mode/src/dev_server.ts b/packages/kbn-cli-dev-mode/src/dev_server.ts index 3daf298c82324..60a279e456e3d 100644 --- a/packages/kbn-cli-dev-mode/src/dev_server.ts +++ b/packages/kbn-cli-dev-mode/src/dev_server.ts @@ -249,5 +249,11 @@ export class DevServer { ) .subscribe(subscriber) ); + + // complete state subjects when run$ completes + subscriber.add(() => { + this.phase$.complete(); + this.ready$.complete(); + }); }); } diff --git a/packages/kbn-cli-dev-mode/src/optimizer.test.ts b/packages/kbn-cli-dev-mode/src/optimizer.test.ts index e3bfb2eb0bb9e..ee8ea5f38ae84 100644 --- a/packages/kbn-cli-dev-mode/src/optimizer.test.ts +++ b/packages/kbn-cli-dev-mode/src/optimizer.test.ts @@ -180,6 +180,7 @@ it('is ready when optimizer phase is success or issue and logs in familiar forma "ready: false", "", "ready: true", + "complete", ] `); diff --git a/packages/kbn-cli-dev-mode/src/optimizer.ts b/packages/kbn-cli-dev-mode/src/optimizer.ts index 750b61140e920..fab566829f7a6 100644 --- a/packages/kbn-cli-dev-mode/src/optimizer.ts +++ b/packages/kbn-cli-dev-mode/src/optimizer.ts @@ -107,14 +107,26 @@ export class Optimizer { }, ]); - this.run$ = runOptimizer(config).pipe( - logOptimizerState(log, config), - tap(({ state }) => { - this.phase$.next(state.phase); - this.ready$.next(state.phase === 'success' || state.phase === 'issue'); - }), - ignoreElements() - ); + this.run$ = new Rx.Observable((subscriber) => { + subscriber.add( + runOptimizer(config) + .pipe( + logOptimizerState(log, config), + tap(({ state }) => { + this.phase$.next(state.phase); + this.ready$.next(state.phase === 'success' || state.phase === 'issue'); + }), + ignoreElements() + ) + .subscribe(subscriber) + ); + + // complete state subjects when run$ completes + subscriber.add(() => { + this.phase$.complete(); + this.ready$.complete(); + }); + }); } getPhase$() { diff --git a/packages/kbn-cli-dev-mode/src/watcher.ts b/packages/kbn-cli-dev-mode/src/watcher.ts index 8e8d2db1b20bb..17993326cfcf3 100644 --- a/packages/kbn-cli-dev-mode/src/watcher.ts +++ b/packages/kbn-cli-dev-mode/src/watcher.ts @@ -103,6 +103,11 @@ export class Watcher { .pipe(ignoreElements()) .subscribe(subscriber) ); + + // complete state subjects when run$ completes + subscriber.add(() => { + this.restart$.complete(); + }); }); serverShouldRestart$() { diff --git a/packages/kbn-pm/dist/index.js b/packages/kbn-pm/dist/index.js index bcb0b6da2a2f8..509ce89f8c02c 100644 --- a/packages/kbn-pm/dist/index.js +++ b/packages/kbn-pm/dist/index.js @@ -209,7 +209,7 @@ async function run(argv) { }, default: { cache: true, - 'force-install': true, + 'force-install': false, offline: false, validate: true }, @@ -8910,8 +8910,11 @@ const BootstrapCommand = { const nonBazelProjectsOnly = await Object(_utils_projects__WEBPACK_IMPORTED_MODULE_4__["getNonBazelProjectsOnly"])(projects); const batchedNonBazelProjects = Object(_utils_projects__WEBPACK_IMPORTED_MODULE_4__["topologicallyBatchProjects"])(nonBazelProjectsOnly, projectGraph); const kibanaProjectPath = ((_projects$get = projects.get('kibana')) === null || _projects$get === void 0 ? void 0 : _projects$get.path) || ''; - const runOffline = (options === null || options === void 0 ? void 0 : options.offline) === true; - const forceInstall = !!options && options['force-install'] === true; // Ensure we have a `node_modules/.yarn-integrity` file as we depend on it + const runOffline = (options === null || options === void 0 ? void 0 : options.offline) === true; // Force install is set in case a flag is passed or + // if the `.yarn-integrity` file is not found which + // will be indicated by the return of yarnIntegrityFileExists. + + const forceInstall = !!options && options['force-install'] === true || !(await Object(_utils_bazel__WEBPACK_IMPORTED_MODULE_9__["yarnIntegrityFileExists"])(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(kibanaProjectPath, 'node_modules'))); // Ensure we have a `node_modules/.yarn-integrity` file as we depend on it // for bazel to know it has to re-install the node_modules after a reset or a clean await Object(_utils_bazel__WEBPACK_IMPORTED_MODULE_9__["ensureYarnIntegrityFileExists"])(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(kibanaProjectPath, 'node_modules')); // Install bazel machinery tools if needed @@ -8925,9 +8928,6 @@ const BootstrapCommand = { // That way non bazel projects could depend on bazel projects but not the other way around // That is only intended during the migration process while non Bazel projects are not removed at all. // - // Until we have our first package build within Bazel we will always need to directly call the yarn rule - // otherwise yarn install won't trigger as we don't have any npm dependency within Bazel - // TODO: Change CLI default in order to not force install as soon as we have our first Bazel package being built if (forceInstall) { await Object(_utils_bazel__WEBPACK_IMPORTED_MODULE_9__["runBazel"])(['run', '@nodejs//:yarn'], runOffline); @@ -9105,6 +9105,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "isDirectory", function() { return isDirectory; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "isFile", function() { return isFile; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "createSymlink", function() { return createSymlink; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "tryRealpath", function() { return tryRealpath; }); /* harmony import */ var cmd_shim__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(132); /* harmony import */ var cmd_shim__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(cmd_shim__WEBPACK_IMPORTED_MODULE_0__); /* harmony import */ var del__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(143); @@ -9137,6 +9138,7 @@ const symlink = Object(util__WEBPACK_IMPORTED_MODULE_5__["promisify"])(fs__WEBPA const chmod = Object(util__WEBPACK_IMPORTED_MODULE_5__["promisify"])(fs__WEBPACK_IMPORTED_MODULE_2___default.a.chmod); const cmdShim = Object(util__WEBPACK_IMPORTED_MODULE_5__["promisify"])(cmd_shim__WEBPACK_IMPORTED_MODULE_0___default.a); const mkdir = Object(util__WEBPACK_IMPORTED_MODULE_5__["promisify"])(fs__WEBPACK_IMPORTED_MODULE_2___default.a.mkdir); +const realpathNative = Object(util__WEBPACK_IMPORTED_MODULE_5__["promisify"])(fs__WEBPACK_IMPORTED_MODULE_2___default.a.realpath.native); const mkdirp = async path => await mkdir(path, { recursive: true }); @@ -9220,6 +9222,20 @@ async function forceCreate(src, dest, type) { await symlink(src, dest, type); } +async function tryRealpath(path) { + let calculatedPath = path; + + try { + calculatedPath = await realpathNative(path); + } catch (error) { + if (error.code !== 'ENOENT') { + throw error; + } + } + + return calculatedPath; +} + /***/ }), /* 132 */ /***/ (function(module, exports, __webpack_require__) { @@ -22981,11 +22997,11 @@ class Project { ensureValidProjectDependency(project) { const relativePathToProject = normalizePath(path__WEBPACK_IMPORTED_MODULE_1___default.a.relative(this.path, project.path)); - const relativePathToProjectIfBazelPkg = normalizePath(path__WEBPACK_IMPORTED_MODULE_1___default.a.relative(this.path, `bazel/bin/packages/${path__WEBPACK_IMPORTED_MODULE_1___default.a.basename(project.path)}`)); + const relativePathToProjectIfBazelPkg = normalizePath(path__WEBPACK_IMPORTED_MODULE_1___default.a.relative(this.path, `${__dirname}/../../../bazel-bin/packages/${path__WEBPACK_IMPORTED_MODULE_1___default.a.basename(project.path)}/npm_module`)); const versionInPackageJson = this.allDependencies[project.name]; const expectedVersionInPackageJson = `link:${relativePathToProject}`; const expectedVersionInPackageJsonIfBazelPkg = `link:${relativePathToProjectIfBazelPkg}`; // TODO: after introduce bazel to build all the packages and completely remove the support for kbn packages - // do not allow child projects to hold dependencies + // do not allow child projects to hold dependencies, unless they are meant to be published externally if (versionInPackageJson === expectedVersionInPackageJson || versionInPackageJson === expectedVersionInPackageJsonIfBazelPkg) { return; @@ -23143,7 +23159,7 @@ const createProductionPackageJson = pkgJson => _objectSpread(_objectSpread({}, p dependencies: transformDependencies(pkgJson.dependencies) }); const isLinkDependency = depVersion => depVersion.startsWith('link:'); -const isBazelPackageDependency = depVersion => depVersion.startsWith('link:bazel/bin/'); +const isBazelPackageDependency = depVersion => depVersion.startsWith('link:bazel-bin/'); /** * Replaces `link:` dependencies with `file:` dependencies. When installing * dependencies, these `file:` dependencies will be copied into `node_modules` @@ -23153,7 +23169,7 @@ const isBazelPackageDependency = depVersion => depVersion.startsWith('link:bazel * will then _copy_ the `file:` dependencies into `node_modules` instead of * symlinking like we do in development. * - * Additionally it also taken care of replacing `link:bazel/bin/` with + * Additionally it also taken care of replacing `link:bazel-bin/` with * `file:` so we can also support the copy of the Bazel packages dist already into * build/packages to be copied into the node_modules */ @@ -23170,7 +23186,7 @@ function transformDependencies(dependencies = {}) { } if (isBazelPackageDependency(depVersion)) { - newDeps[name] = depVersion.replace('link:bazel/bin/', 'file:'); + newDeps[name] = depVersion.replace('link:bazel-bin/', 'file:').replace('/npm_module', ''); continue; } @@ -48065,8 +48081,10 @@ function addProjectToTree(tree, pathParts, project) { "use strict"; __webpack_require__.r(__webpack_exports__); -/* harmony import */ var _ensure_yarn_integrity_exists__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(373); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "ensureYarnIntegrityFileExists", function() { return _ensure_yarn_integrity_exists__WEBPACK_IMPORTED_MODULE_0__["ensureYarnIntegrityFileExists"]; }); +/* harmony import */ var _yarn_integrity__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(373); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "yarnIntegrityFileExists", function() { return _yarn_integrity__WEBPACK_IMPORTED_MODULE_0__["yarnIntegrityFileExists"]; }); + +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "ensureYarnIntegrityFileExists", function() { return _yarn_integrity__WEBPACK_IMPORTED_MODULE_0__["ensureYarnIntegrityFileExists"]; }); /* harmony import */ var _get_cache_folders__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(374); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "getBazelDiskCacheFolder", function() { return _get_cache_folders__WEBPACK_IMPORTED_MODULE_1__["getBazelDiskCacheFolder"]; }); @@ -48099,6 +48117,7 @@ __webpack_require__.r(__webpack_exports__); "use strict"; __webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "yarnIntegrityFileExists", function() { return yarnIntegrityFileExists; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ensureYarnIntegrityFileExists", function() { return ensureYarnIntegrityFileExists; }); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(4); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_0__); @@ -48112,9 +48131,27 @@ __webpack_require__.r(__webpack_exports__); */ +async function yarnIntegrityFileExists(nodeModulesPath) { + try { + const nodeModulesRealPath = await Object(_fs__WEBPACK_IMPORTED_MODULE_1__["tryRealpath"])(nodeModulesPath); + const yarnIntegrityFilePath = Object(path__WEBPACK_IMPORTED_MODULE_0__["join"])(nodeModulesRealPath, '.yarn-integrity'); // check if the file already exists + + if (await Object(_fs__WEBPACK_IMPORTED_MODULE_1__["isFile"])(yarnIntegrityFilePath)) { + return true; + } + } catch {// no-op + } + + return false; +} async function ensureYarnIntegrityFileExists(nodeModulesPath) { try { - await Object(_fs__WEBPACK_IMPORTED_MODULE_1__["writeFile"])(Object(path__WEBPACK_IMPORTED_MODULE_0__["join"])(nodeModulesPath, '.yarn-integrity'), '', { + const nodeModulesRealPath = await Object(_fs__WEBPACK_IMPORTED_MODULE_1__["tryRealpath"])(nodeModulesPath); + const yarnIntegrityFilePath = Object(path__WEBPACK_IMPORTED_MODULE_0__["join"])(nodeModulesRealPath, '.yarn-integrity'); // ensure node_modules folder is created + + await Object(_fs__WEBPACK_IMPORTED_MODULE_1__["mkdirp"])(nodeModulesRealPath); // write a blank file in case it doesn't exists + + await Object(_fs__WEBPACK_IMPORTED_MODULE_1__["writeFile"])(yarnIntegrityFilePath, '', { flag: 'wx' }); } catch {// no-op @@ -63656,7 +63693,7 @@ async function buildBazelProductionProjects({ const projectNames = [...projects.values()].map(project => project.name); _utils_log__WEBPACK_IMPORTED_MODULE_6__["log"].info(`Preparing Bazel projects production build for [${projectNames.join(', ')}]`); await Object(_utils_bazel__WEBPACK_IMPORTED_MODULE_4__["runBazel"])(['build', '//packages:build']); - _utils_log__WEBPACK_IMPORTED_MODULE_6__["log"].info(`All Bazel projects production builds for [${projectNames.join(', ')}] are complete}]`); + _utils_log__WEBPACK_IMPORTED_MODULE_6__["log"].info(`All Bazel projects production builds for [${projectNames.join(', ')}] are complete`); for (const project of projects.values()) { await copyToBuild(project, kibanaRoot, buildRoot); @@ -63680,7 +63717,7 @@ async function copyToBuild(project, kibanaRoot, buildRoot) { const relativeProjectPath = Object(path__WEBPACK_IMPORTED_MODULE_2__["relative"])(kibanaRoot, project.path); const buildProjectPath = Object(path__WEBPACK_IMPORTED_MODULE_2__["resolve"])(buildRoot, relativeProjectPath); await cpy__WEBPACK_IMPORTED_MODULE_0___default()(['**/*'], buildProjectPath, { - cwd: Object(path__WEBPACK_IMPORTED_MODULE_2__["join"])(kibanaRoot, 'bazel', 'bin', 'packages', Object(path__WEBPACK_IMPORTED_MODULE_2__["basename"])(buildProjectPath), 'npm_module'), + cwd: Object(path__WEBPACK_IMPORTED_MODULE_2__["join"])(kibanaRoot, 'bazel-bin', 'packages', Object(path__WEBPACK_IMPORTED_MODULE_2__["basename"])(buildProjectPath), 'npm_module'), dot: true, onlyFiles: true, parents: true @@ -63702,12 +63739,12 @@ async function applyCorrectPermissions(project, kibanaRoot, buildRoot) { const buildProjectPath = Object(path__WEBPACK_IMPORTED_MODULE_2__["resolve"])(buildRoot, relativeProjectPath); const allPluginPaths = await globby__WEBPACK_IMPORTED_MODULE_1___default()([`**/*`], { onlyFiles: false, - cwd: Object(path__WEBPACK_IMPORTED_MODULE_2__["join"])(kibanaRoot, 'bazel', 'bin', 'packages', Object(path__WEBPACK_IMPORTED_MODULE_2__["basename"])(buildProjectPath), 'npm_module'), + cwd: buildProjectPath, dot: true }); for (const pluginPath of allPluginPaths) { - const resolvedPluginPath = Object(path__WEBPACK_IMPORTED_MODULE_2__["resolve"])(buildRoot, pluginPath); + const resolvedPluginPath = Object(path__WEBPACK_IMPORTED_MODULE_2__["resolve"])(buildProjectPath, pluginPath); if (await Object(_utils_fs__WEBPACK_IMPORTED_MODULE_5__["isFile"])(resolvedPluginPath)) { await Object(_utils_fs__WEBPACK_IMPORTED_MODULE_5__["chmod"])(resolvedPluginPath, 0o644); diff --git a/packages/kbn-pm/src/cli.ts b/packages/kbn-pm/src/cli.ts index 6d033b4121d99..f6ea4d7124ab2 100644 --- a/packages/kbn-pm/src/cli.ts +++ b/packages/kbn-pm/src/cli.ts @@ -75,7 +75,7 @@ export async function run(argv: string[]) { }, default: { cache: true, - 'force-install': true, + 'force-install': false, offline: false, validate: true, }, diff --git a/packages/kbn-pm/src/commands/bootstrap.ts b/packages/kbn-pm/src/commands/bootstrap.ts index 4a6a43ff2d91f..b383a52be63f5 100644 --- a/packages/kbn-pm/src/commands/bootstrap.ts +++ b/packages/kbn-pm/src/commands/bootstrap.ts @@ -17,7 +17,12 @@ import { getAllChecksums } from '../utils/project_checksums'; import { BootstrapCacheFile } from '../utils/bootstrap_cache_file'; import { readYarnLock } from '../utils/yarn_lock'; import { validateDependencies } from '../utils/validate_dependencies'; -import { ensureYarnIntegrityFileExists, installBazelTools, runBazel } from '../utils/bazel'; +import { + ensureYarnIntegrityFileExists, + installBazelTools, + runBazel, + yarnIntegrityFileExists, +} from '../utils/bazel'; export const BootstrapCommand: ICommand = { description: 'Install dependencies and crosslink projects', @@ -33,7 +38,13 @@ export const BootstrapCommand: ICommand = { const batchedNonBazelProjects = topologicallyBatchProjects(nonBazelProjectsOnly, projectGraph); const kibanaProjectPath = projects.get('kibana')?.path || ''; const runOffline = options?.offline === true; - const forceInstall = !!options && options['force-install'] === true; + + // Force install is set in case a flag is passed or + // if the `.yarn-integrity` file is not found which + // will be indicated by the return of yarnIntegrityFileExists. + const forceInstall = + (!!options && options['force-install'] === true) || + !(await yarnIntegrityFileExists(resolve(kibanaProjectPath, 'node_modules'))); // Ensure we have a `node_modules/.yarn-integrity` file as we depend on it // for bazel to know it has to re-install the node_modules after a reset or a clean @@ -51,9 +62,6 @@ export const BootstrapCommand: ICommand = { // That way non bazel projects could depend on bazel projects but not the other way around // That is only intended during the migration process while non Bazel projects are not removed at all. // - // Until we have our first package build within Bazel we will always need to directly call the yarn rule - // otherwise yarn install won't trigger as we don't have any npm dependency within Bazel - // TODO: Change CLI default in order to not force install as soon as we have our first Bazel package being built if (forceInstall) { await runBazel(['run', '@nodejs//:yarn'], runOffline); } diff --git a/packages/kbn-pm/src/production/build_bazel_production_projects.ts b/packages/kbn-pm/src/production/build_bazel_production_projects.ts index 313622d44276a..07c0b651f5ad1 100644 --- a/packages/kbn-pm/src/production/build_bazel_production_projects.ts +++ b/packages/kbn-pm/src/production/build_bazel_production_projects.ts @@ -37,7 +37,7 @@ export async function buildBazelProductionProjects({ log.info(`Preparing Bazel projects production build for [${projectNames.join(', ')}]`); await runBazel(['build', '//packages:build']); - log.info(`All Bazel projects production builds for [${projectNames.join(', ')}] are complete}]`); + log.info(`All Bazel projects production builds for [${projectNames.join(', ')}] are complete`); for (const project of projects.values()) { await copyToBuild(project, kibanaRoot, buildRoot); @@ -62,7 +62,7 @@ async function copyToBuild(project: Project, kibanaRoot: string, buildRoot: stri const buildProjectPath = resolve(buildRoot, relativeProjectPath); await copy(['**/*'], buildProjectPath, { - cwd: join(kibanaRoot, 'bazel', 'bin', 'packages', basename(buildProjectPath), 'npm_module'), + cwd: join(kibanaRoot, 'bazel-bin', 'packages', basename(buildProjectPath), 'npm_module'), dot: true, onlyFiles: true, parents: true, @@ -88,12 +88,12 @@ async function applyCorrectPermissions(project: Project, kibanaRoot: string, bui const buildProjectPath = resolve(buildRoot, relativeProjectPath); const allPluginPaths = await globby([`**/*`], { onlyFiles: false, - cwd: join(kibanaRoot, 'bazel', 'bin', 'packages', basename(buildProjectPath), 'npm_module'), + cwd: buildProjectPath, dot: true, }); for (const pluginPath of allPluginPaths) { - const resolvedPluginPath = resolve(buildRoot, pluginPath); + const resolvedPluginPath = resolve(buildProjectPath, pluginPath); if (await isFile(resolvedPluginPath)) { await chmod(resolvedPluginPath, 0o644); } diff --git a/packages/kbn-pm/src/utils/__snapshots__/link_project_executables.test.ts.snap b/packages/kbn-pm/src/utils/__snapshots__/link_project_executables.test.ts.snap index c037c2a4976b4..8aeae04c265cf 100644 --- a/packages/kbn-pm/src/utils/__snapshots__/link_project_executables.test.ts.snap +++ b/packages/kbn-pm/src/utils/__snapshots__/link_project_executables.test.ts.snap @@ -11,6 +11,7 @@ Object { "mkdirp": Array [], "readFile": Array [], "rmdirp": Array [], + "tryRealpath": Array [], "unlink": Array [], "writeFile": Array [], } @@ -27,6 +28,7 @@ Object { "mkdirp": Array [], "readFile": Array [], "rmdirp": Array [], + "tryRealpath": Array [], "unlink": Array [], "writeFile": Array [], } diff --git a/packages/kbn-pm/src/utils/bazel/ensure_yarn_integrity_exists.ts b/packages/kbn-pm/src/utils/bazel/ensure_yarn_integrity_exists.ts deleted file mode 100644 index 90786bc0ea55e..0000000000000 --- a/packages/kbn-pm/src/utils/bazel/ensure_yarn_integrity_exists.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 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 { join } from 'path'; -import { writeFile } from '../fs'; - -export async function ensureYarnIntegrityFileExists(nodeModulesPath: string) { - try { - await writeFile(join(nodeModulesPath, '.yarn-integrity'), '', { flag: 'wx' }); - } catch { - // no-op - } -} diff --git a/packages/kbn-pm/src/utils/bazel/index.ts b/packages/kbn-pm/src/utils/bazel/index.ts index 0b755ba2446a0..a3651039161b8 100644 --- a/packages/kbn-pm/src/utils/bazel/index.ts +++ b/packages/kbn-pm/src/utils/bazel/index.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -export * from './ensure_yarn_integrity_exists'; +export * from './yarn_integrity'; export * from './get_cache_folders'; export * from './install_tools'; export * from './run'; diff --git a/packages/kbn-pm/src/utils/bazel/yarn_integrity.ts b/packages/kbn-pm/src/utils/bazel/yarn_integrity.ts new file mode 100644 index 0000000000000..3a72f5ca080b8 --- /dev/null +++ b/packages/kbn-pm/src/utils/bazel/yarn_integrity.ts @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { join } from 'path'; +import { isFile, mkdirp, tryRealpath, writeFile } from '../fs'; + +export async function yarnIntegrityFileExists(nodeModulesPath: string) { + try { + const nodeModulesRealPath = await tryRealpath(nodeModulesPath); + const yarnIntegrityFilePath = join(nodeModulesRealPath, '.yarn-integrity'); + + // check if the file already exists + if (await isFile(yarnIntegrityFilePath)) { + return true; + } + } catch { + // no-op + } + + return false; +} + +export async function ensureYarnIntegrityFileExists(nodeModulesPath: string) { + try { + const nodeModulesRealPath = await tryRealpath(nodeModulesPath); + const yarnIntegrityFilePath = join(nodeModulesRealPath, '.yarn-integrity'); + + // ensure node_modules folder is created + await mkdirp(nodeModulesRealPath); + + // write a blank file in case it doesn't exists + await writeFile(yarnIntegrityFilePath, '', { flag: 'wx' }); + } catch { + // no-op + } +} diff --git a/packages/kbn-pm/src/utils/fs.ts b/packages/kbn-pm/src/utils/fs.ts index dd961b8321446..5739d319e08e7 100644 --- a/packages/kbn-pm/src/utils/fs.ts +++ b/packages/kbn-pm/src/utils/fs.ts @@ -20,6 +20,7 @@ const symlink = promisify(fs.symlink); export const chmod = promisify(fs.chmod); const cmdShim = promisify(cmdShimCb); const mkdir = promisify(fs.mkdir); +const realpathNative = promisify(fs.realpath.native); export const mkdirp = async (path: string) => await mkdir(path, { recursive: true }); export const rmdirp = async (path: string) => await del(path, { force: true }); export const unlink = promisify(fs.unlink); @@ -96,3 +97,17 @@ async function forceCreate(src: string, dest: string, type: string) { await symlink(src, dest, type); } + +export async function tryRealpath(path: string): Promise { + let calculatedPath = path; + + try { + calculatedPath = await realpathNative(path); + } catch (error) { + if (error.code !== 'ENOENT') { + throw error; + } + } + + return calculatedPath; +} diff --git a/packages/kbn-pm/src/utils/package_json.ts b/packages/kbn-pm/src/utils/package_json.ts index b405b544ab800..e635c2566e65a 100644 --- a/packages/kbn-pm/src/utils/package_json.ts +++ b/packages/kbn-pm/src/utils/package_json.ts @@ -35,7 +35,7 @@ export const createProductionPackageJson = (pkgJson: IPackageJson) => ({ export const isLinkDependency = (depVersion: string) => depVersion.startsWith('link:'); export const isBazelPackageDependency = (depVersion: string) => - depVersion.startsWith('link:bazel/bin/'); + depVersion.startsWith('link:bazel-bin/'); /** * Replaces `link:` dependencies with `file:` dependencies. When installing @@ -46,7 +46,7 @@ export const isBazelPackageDependency = (depVersion: string) => * will then _copy_ the `file:` dependencies into `node_modules` instead of * symlinking like we do in development. * - * Additionally it also taken care of replacing `link:bazel/bin/` with + * Additionally it also taken care of replacing `link:bazel-bin/` with * `file:` so we can also support the copy of the Bazel packages dist already into * build/packages to be copied into the node_modules */ @@ -61,7 +61,7 @@ export function transformDependencies(dependencies: IPackageDependencies = {}) { } if (isBazelPackageDependency(depVersion)) { - newDeps[name] = depVersion.replace('link:bazel/bin/', 'file:'); + newDeps[name] = depVersion.replace('link:bazel-bin/', 'file:').replace('/npm_module', ''); continue; } diff --git a/packages/kbn-pm/src/utils/project.ts b/packages/kbn-pm/src/utils/project.ts index 797a9a36df78f..5d2a0547b2577 100644 --- a/packages/kbn-pm/src/utils/project.ts +++ b/packages/kbn-pm/src/utils/project.ts @@ -92,7 +92,10 @@ export class Project { public ensureValidProjectDependency(project: Project) { const relativePathToProject = normalizePath(Path.relative(this.path, project.path)); const relativePathToProjectIfBazelPkg = normalizePath( - Path.relative(this.path, `bazel/bin/packages/${Path.basename(project.path)}`) + Path.relative( + this.path, + `${__dirname}/../../../bazel-bin/packages/${Path.basename(project.path)}/npm_module` + ) ); const versionInPackageJson = this.allDependencies[project.name]; @@ -100,7 +103,7 @@ export class Project { const expectedVersionInPackageJsonIfBazelPkg = `link:${relativePathToProjectIfBazelPkg}`; // TODO: after introduce bazel to build all the packages and completely remove the support for kbn packages - // do not allow child projects to hold dependencies + // do not allow child projects to hold dependencies, unless they are meant to be published externally if ( versionInPackageJson === expectedVersionInPackageJson || versionInPackageJson === expectedVersionInPackageJsonIfBazelPkg diff --git a/packages/kbn-test/jest-preset.js b/packages/kbn-test/jest-preset.js index 5d45a240cb118..96b17b28f7098 100644 --- a/packages/kbn-test/jest-preset.js +++ b/packages/kbn-test/jest-preset.js @@ -107,4 +107,7 @@ module.exports = { '!**/*.d.ts', '!**/index.{js,ts}', ], + + // A custom resolver to preserve symlinks by default + resolver: '/packages/kbn-test/target/jest/setup/preserve_symlinks_resolver.js', }; diff --git a/packages/kbn-test/src/jest/setup/preserve_symlinks_resolver.js b/packages/kbn-test/src/jest/setup/preserve_symlinks_resolver.js new file mode 100644 index 0000000000000..711bf2c9aa189 --- /dev/null +++ b/packages/kbn-test/src/jest/setup/preserve_symlinks_resolver.js @@ -0,0 +1,30 @@ +/* + * 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. + */ + +// Inspired in a discussion found at https://github.com/facebook/jest/issues/5356 as Jest currently doesn't +// offer any other option to preserve symlinks. +// +// It would be available once https://github.com/facebook/jest/pull/9976 got merged. + +const resolve = require('resolve'); + +module.exports = (request, options) => { + try { + return resolve.sync(request, { + basedir: options.basedir, + extensions: options.extensions, + preserveSymlinks: true, + }); + } catch (error) { + if (error.code === 'MODULE_NOT_FOUND') { + return options.defaultResolver(request, options); + } + + throw error; + } +}; diff --git a/src/plugins/dashboard/public/application/dashboard_router.tsx b/src/plugins/dashboard/public/application/dashboard_router.tsx index f981b135c4359..e5281a257ee13 100644 --- a/src/plugins/dashboard/public/application/dashboard_router.tsx +++ b/src/plugins/dashboard/public/application/dashboard_router.tsx @@ -84,6 +84,7 @@ export async function mountApp({ const spacesApi = pluginsStart.spacesOss?.isSpacesAvailable ? pluginsStart.spacesOss : undefined; const activeSpaceId = spacesApi && (await spacesApi.activeSpace$.pipe(first()).toPromise())?.id; + let globalEmbedSettings: DashboardEmbedSettings | undefined; const dashboardServices: DashboardAppServices = { navigation, @@ -149,9 +150,6 @@ export async function mountApp({ const getDashboardEmbedSettings = ( routeParams: ParsedQuery ): DashboardEmbedSettings | undefined => { - if (!routeParams.embed) { - return undefined; - } return { forceShowTopNavMenu: Boolean(routeParams[dashboardUrlParams.showTopMenu]), forceShowQueryInput: Boolean(routeParams[dashboardUrlParams.showQueryInput]), @@ -162,11 +160,13 @@ export async function mountApp({ const renderDashboard = (routeProps: RouteComponentProps<{ id?: string }>) => { const routeParams = parse(routeProps.history.location.search); - const embedSettings = getDashboardEmbedSettings(routeParams); + if (routeParams.embed && !globalEmbedSettings) { + globalEmbedSettings = getDashboardEmbedSettings(routeParams); + } return ( redirect(routeProps, props)} /> diff --git a/src/plugins/timelion/public/directives/timelion_help/timelion_help.html b/src/plugins/timelion/public/directives/timelion_help/timelion_help.html index 69f22959f5332..a44c838989397 100644 --- a/src/plugins/timelion/public/directives/timelion_help/timelion_help.html +++ b/src/plugins/timelion/public/directives/timelion_help/timelion_help.html @@ -207,7 +207,7 @@ >

@@ -406,7 +406,7 @@ >

- | APIReturnType<'GET /api/apm/correlations/slow_transactions'>; + | APIReturnType<'GET /api/apm/correlations/errors/failed_transactions'> + | APIReturnType<'GET /api/apm/correlations/latency/slow_transactions'>; -type SignificantTerm = NonNullable< - NonNullable['significantTerms'] ->[0]; +type SignificantTerm = CorrelationsApiResponse['significantTerms'][0]; export type SelectedSignificantTerm = Pick< SignificantTerm, diff --git a/x-pack/plugins/apm/public/components/app/correlations/error_correlations.tsx b/x-pack/plugins/apm/public/components/app/correlations/error_correlations.tsx index c3b5f52dd84b7..7fb7444a52f84 100644 --- a/x-pack/plugins/apm/public/components/app/correlations/error_correlations.tsx +++ b/x-pack/plugins/apm/public/components/app/correlations/error_correlations.tsx @@ -34,8 +34,12 @@ import { useFieldNames } from './use_field_names'; import { useLocalStorage } from '../../../hooks/useLocalStorage'; import { useUiTracker } from '../../../../../observability/public'; +type OverallErrorsApiResponse = NonNullable< + APIReturnType<'GET /api/apm/correlations/errors/overall_timeseries'> +>; + type CorrelationsApiResponse = NonNullable< - APIReturnType<'GET /api/apm/correlations/failed_transactions'> + APIReturnType<'GET /api/apm/correlations/errors/failed_transactions'> >; interface Props { @@ -65,11 +69,41 @@ export function ErrorCorrelations({ onClose }: Props) { ); const hasFieldNames = fieldNames.length > 0; - const { data, status } = useFetcher( + const { data: overallData, status: overallStatus } = useFetcher( + (callApmApi) => { + if (start && end) { + return callApmApi({ + endpoint: 'GET /api/apm/correlations/errors/overall_timeseries', + params: { + query: { + environment, + kuery, + serviceName, + transactionName, + transactionType, + start, + end, + }, + }, + }); + } + }, + [ + environment, + kuery, + serviceName, + start, + end, + transactionName, + transactionType, + ] + ); + + const { data: correlationsData, status: correlationsStatus } = useFetcher( (callApmApi) => { if (start && end && hasFieldNames) { return callApmApi({ - endpoint: 'GET /api/apm/correlations/failed_transactions', + endpoint: 'GET /api/apm/correlations/errors/failed_transactions', params: { query: { environment, @@ -125,8 +159,9 @@ export function ErrorCorrelations({ onClose }: Props) { @@ -136,8 +171,12 @@ export function ErrorCorrelations({ onClose }: Props) { 'xpack.apm.correlations.error.percentageColumnName', { defaultMessage: '% of failed transactions' } )} - significantTerms={hasFieldNames ? data?.significantTerms : []} - status={status} + significantTerms={ + hasFieldNames && correlationsData?.significantTerms + ? correlationsData.significantTerms + : [] + } + status={correlationsStatus} setSelectedSignificantTerm={setSelectedSignificantTerm} onFilter={onClose} /> @@ -151,10 +190,9 @@ export function ErrorCorrelations({ onClose }: Props) { } function getSelectedTimeseries( - data: CorrelationsApiResponse, + significantTerms: CorrelationsApiResponse['significantTerms'], selectedSignificantTerm: SelectedSignificantTerm ) { - const { significantTerms } = data; if (!significantTerms) { return []; } @@ -168,11 +206,13 @@ function getSelectedTimeseries( } function ErrorTimeseriesChart({ - data, + overallData, + correlationsData, selectedSignificantTerm, status, }: { - data?: CorrelationsApiResponse; + overallData?: OverallErrorsApiResponse; + correlationsData?: CorrelationsApiResponse; selectedSignificantTerm: SelectedSignificantTerm | null; status: FETCH_STATUS; }) { @@ -180,7 +220,7 @@ function ErrorTimeseriesChart({ const dateFormatter = timeFormatter('HH:mm:ss'); return ( - + @@ -206,11 +246,11 @@ function ErrorTimeseriesChart({ yScaleType={ScaleType.Linear} xAccessor={'x'} yAccessors={['y']} - data={data?.overall?.timeseries ?? []} + data={overallData?.overall?.timeseries ?? []} curve={CurveType.CURVE_MONOTONE_X} /> - {data && selectedSignificantTerm ? ( + {correlationsData && selectedSignificantTerm ? ( ) : null} diff --git a/x-pack/plugins/apm/public/components/app/correlations/latency_correlations.tsx b/x-pack/plugins/apm/public/components/app/correlations/latency_correlations.tsx index 77571421ed00e..e65bad8088c17 100644 --- a/x-pack/plugins/apm/public/components/app/correlations/latency_correlations.tsx +++ b/x-pack/plugins/apm/public/components/app/correlations/latency_correlations.tsx @@ -32,8 +32,12 @@ import { useFieldNames } from './use_field_names'; import { useLocalStorage } from '../../../hooks/useLocalStorage'; import { useUiTracker } from '../../../../../observability/public'; +type OverallLatencyApiResponse = NonNullable< + APIReturnType<'GET /api/apm/correlations/latency/overall_distribution'> +>; + type CorrelationsApiResponse = NonNullable< - APIReturnType<'GET /api/apm/correlations/slow_transactions'> + APIReturnType<'GET /api/apm/correlations/latency/slow_transactions'> >; interface Props { @@ -71,11 +75,45 @@ export function LatencyCorrelations({ onClose }: Props) { 75 ); - const { data, status } = useFetcher( + const { data: overallData, status: overallStatus } = useFetcher( (callApmApi) => { - if (start && end && hasFieldNames) { + if (start && end) { return callApmApi({ - endpoint: 'GET /api/apm/correlations/slow_transactions', + endpoint: 'GET /api/apm/correlations/latency/overall_distribution', + params: { + query: { + environment, + kuery, + serviceName, + transactionName, + transactionType, + start, + end, + }, + }, + }); + } + }, + [ + environment, + kuery, + serviceName, + start, + end, + transactionName, + transactionType, + ] + ); + + const maxLatency = overallData?.maxLatency; + const distributionInterval = overallData?.distributionInterval; + const fieldNamesCommaSeparated = fieldNames.join(','); + + const { data: correlationsData, status: correlationsStatus } = useFetcher( + (callApmApi) => { + if (start && end && hasFieldNames && maxLatency && distributionInterval) { + return callApmApi({ + endpoint: 'GET /api/apm/correlations/latency/slow_transactions', params: { query: { environment, @@ -86,7 +124,9 @@ export function LatencyCorrelations({ onClose }: Props) { start, end, durationPercentile: durationPercentile.toString(10), - fieldNames: fieldNames.join(','), + fieldNames: fieldNamesCommaSeparated, + maxLatency: maxLatency.toString(10), + distributionInterval: distributionInterval.toString(10), }, }, }); @@ -101,8 +141,10 @@ export function LatencyCorrelations({ onClose }: Props) { transactionName, transactionType, durationPercentile, - fieldNames, + fieldNamesCommaSeparated, hasFieldNames, + maxLatency, + distributionInterval, ] ); @@ -134,8 +176,13 @@ export function LatencyCorrelations({ onClose }: Props) { @@ -147,8 +194,12 @@ export function LatencyCorrelations({ onClose }: Props) { 'xpack.apm.correlations.latency.percentageColumnName', { defaultMessage: '% of slow transactions' } )} - significantTerms={hasFieldNames ? data?.significantTerms : []} - status={status} + significantTerms={ + hasFieldNames && correlationsData + ? correlationsData?.significantTerms + : [] + } + status={correlationsStatus} setSelectedSignificantTerm={setSelectedSignificantTerm} onFilter={onClose} /> @@ -167,25 +218,23 @@ export function LatencyCorrelations({ onClose }: Props) { ); } -function getDistributionYMax(data?: CorrelationsApiResponse) { - if (!data?.overall) { - return 0; +function getAxisMaxes(data?: OverallLatencyApiResponse) { + if (!data?.overallDistribution) { + return { xMax: 0, yMax: 0 }; } - - const yValues = [ - ...data.overall.distribution.map((p) => p.y ?? 0), - ...data.significantTerms.flatMap((term) => - term.distribution.map((p) => p.y ?? 0) - ), - ]; - return Math.max(...yValues); + const { overallDistribution } = data; + const xValues = overallDistribution.map((p) => p.x ?? 0); + const yValues = overallDistribution.map((p) => p.y ?? 0); + return { + xMax: Math.max(...xValues), + yMax: Math.max(...yValues), + }; } function getSelectedDistribution( - data: CorrelationsApiResponse, + significantTerms: CorrelationsApiResponse['significantTerms'], selectedSignificantTerm: SelectedSignificantTerm ) { - const { significantTerms } = data; if (!significantTerms) { return []; } @@ -199,23 +248,22 @@ function getSelectedDistribution( } function LatencyDistributionChart({ - data, + overallData, + correlationsData, selectedSignificantTerm, status, }: { - data?: CorrelationsApiResponse; + overallData?: OverallLatencyApiResponse; + correlationsData?: CorrelationsApiResponse['significantTerms']; selectedSignificantTerm: SelectedSignificantTerm | null; status: FETCH_STATUS; }) { const theme = useTheme(); - const xMax = Math.max( - ...(data?.overall?.distribution.map((p) => p.x ?? 0) ?? []) - ); + const { xMax, yMax } = getAxisMaxes(overallData); const durationFormatter = getDurationFormatter(xMax); - const yMax = getDistributionYMax(data); return ( - + { const start = durationFormatter(obj.value); const end = durationFormatter( - obj.value + data?.distributionInterval + obj.value + overallData?.distributionInterval ); return `${start.value} - ${end.formatted}`; @@ -254,12 +302,12 @@ function LatencyDistributionChart({ xAccessor={'x'} yAccessors={['y']} color={theme.eui.euiColorVis1} - data={data?.overall?.distribution || []} + data={overallData?.overallDistribution || []} minBarHeight={5} tickFormat={(d) => `${roundFloat(d)}%`} /> - {data && selectedSignificantTerm ? ( + {correlationsData && selectedSignificantTerm ? ( `${roundFloat(d)}%`} /> diff --git a/x-pack/plugins/apm/server/lib/correlations/get_correlations_for_failed_transactions/index.ts b/x-pack/plugins/apm/server/lib/correlations/errors/get_correlations_for_failed_transactions.ts similarity index 63% rename from x-pack/plugins/apm/server/lib/correlations/get_correlations_for_failed_transactions/index.ts rename to x-pack/plugins/apm/server/lib/correlations/errors/get_correlations_for_failed_transactions.ts index c668f3bb28713..8ee469c9a93c7 100644 --- a/x-pack/plugins/apm/server/lib/correlations/get_correlations_for_failed_transactions/index.ts +++ b/x-pack/plugins/apm/server/lib/correlations/errors/get_correlations_for_failed_transactions.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { isEmpty, omit, merge } from 'lodash'; +import { isEmpty, omit } from 'lodash'; import { EventOutcome } from '../../../../common/event_outcome'; import { processSignificantTermAggs, @@ -13,65 +13,25 @@ import { } from '../process_significant_term_aggs'; import { AggregationOptionsByType } from '../../../../../../../typings/elasticsearch'; import { ESFilter } from '../../../../../../../typings/elasticsearch'; -import { - environmentQuery, - rangeQuery, - kqlQuery, -} from '../../../../server/utils/queries'; -import { - EVENT_OUTCOME, - SERVICE_NAME, - TRANSACTION_NAME, - TRANSACTION_TYPE, - PROCESSOR_EVENT, -} from '../../../../common/elasticsearch_fieldnames'; +import { EVENT_OUTCOME } from '../../../../common/elasticsearch_fieldnames'; import { ProcessorEvent } from '../../../../common/processor_event'; import { Setup, SetupTimeRange } from '../../helpers/setup_request'; import { getBucketSize } from '../../helpers/get_bucket_size'; import { - getOutcomeAggregation, + getTimeseriesAggregation, getTransactionErrorRateTimeSeries, } from '../../helpers/transaction_error_rate'; import { withApmSpan } from '../../../utils/with_apm_span'; +import { CorrelationsOptions, getCorrelationsFilters } from '../get_filters'; -export async function getCorrelationsForFailedTransactions({ - environment, - kuery, - serviceName, - transactionType, - transactionName, - fieldNames, - setup, -}: { - environment?: string; - kuery?: string; - serviceName: string | undefined; - transactionType: string | undefined; - transactionName: string | undefined; +interface Options extends CorrelationsOptions { fieldNames: string[]; - setup: Setup & SetupTimeRange; -}) { +} +export async function getCorrelationsForFailedTransactions(options: Options) { return withApmSpan('get_correlations_for_failed_transactions', async () => { - const { start, end, apmEventClient } = setup; - - const backgroundFilters: ESFilter[] = [ - { term: { [PROCESSOR_EVENT]: ProcessorEvent.transaction } }, - ...rangeQuery(start, end), - ...environmentQuery(environment), - ...kqlQuery(kuery), - ]; - - if (serviceName) { - backgroundFilters.push({ term: { [SERVICE_NAME]: serviceName } }); - } - - if (transactionType) { - backgroundFilters.push({ term: { [TRANSACTION_TYPE]: transactionType } }); - } - - if (transactionName) { - backgroundFilters.push({ term: { [TRANSACTION_NAME]: transactionName } }); - } + const { fieldNames, setup } = options; + const { apmEventClient } = setup; + const filters = getCorrelationsFilters(options); const params = { apm: { events: [ProcessorEvent.transaction] }, @@ -79,7 +39,7 @@ export async function getCorrelationsForFailedTransactions({ body: { size: 0, query: { - bool: { filter: backgroundFilters }, + bool: { filter: filters }, }, aggs: { failed_transactions: { @@ -95,7 +55,7 @@ export async function getCorrelationsForFailedTransactions({ field: fieldName, background_filter: { bool: { - filter: backgroundFilters, + filter: filters, must_not: { term: { [EVENT_OUTCOME]: EventOutcome.failure }, }, @@ -112,7 +72,7 @@ export async function getCorrelationsForFailedTransactions({ const response = await apmEventClient.search(params); if (!response.aggregations) { - return {}; + return { significantTerms: [] }; } const sigTermAggs = omit( @@ -121,17 +81,17 @@ export async function getCorrelationsForFailedTransactions({ ); const topSigTerms = processSignificantTermAggs({ sigTermAggs }); - return getErrorRateTimeSeries({ setup, backgroundFilters, topSigTerms }); + return getErrorRateTimeSeries({ setup, filters, topSigTerms }); }); } export async function getErrorRateTimeSeries({ setup, - backgroundFilters, + filters, topSigTerms, }: { setup: Setup & SetupTimeRange; - backgroundFilters: ESFilter[]; + filters: ESFilter[]; topSigTerms: TopSigTerm[]; }) { return withApmSpan('get_error_rate_timeseries', async () => { @@ -139,20 +99,10 @@ export async function getErrorRateTimeSeries({ const { intervalString } = getBucketSize({ start, end, numBuckets: 15 }); if (isEmpty(topSigTerms)) { - return {}; + return { significantTerms: [] }; } - const timeseriesAgg = { - date_histogram: { - field: '@timestamp', - fixed_interval: intervalString, - min_doc_count: 0, - extended_bounds: { min: start, max: end }, - }, - aggs: { - outcomes: getOutcomeAggregation(), - }, - }; + const timeseriesAgg = getTimeseriesAggregation(start, end, intervalString); const perTermAggs = topSigTerms.reduce( (acc, term, index) => { @@ -175,8 +125,8 @@ export async function getErrorRateTimeSeries({ apm: { events: [ProcessorEvent.transaction] }, body: { size: 0, - query: { bool: { filter: backgroundFilters } }, - aggs: merge({ timeseries: timeseriesAgg }, perTermAggs), + query: { bool: { filter: filters } }, + aggs: perTermAggs, }, }; @@ -184,15 +134,10 @@ export async function getErrorRateTimeSeries({ const { aggregations } = response; if (!aggregations) { - return {}; + return { significantTerms: [] }; } return { - overall: { - timeseries: getTransactionErrorRateTimeSeries( - aggregations.timeseries.buckets - ), - }, significantTerms: topSigTerms.map((topSig, index) => { const agg = aggregations[`term_${index}`]!; diff --git a/x-pack/plugins/apm/server/lib/correlations/errors/get_overall_error_timeseries.ts b/x-pack/plugins/apm/server/lib/correlations/errors/get_overall_error_timeseries.ts new file mode 100644 index 0000000000000..9387e64a51e01 --- /dev/null +++ b/x-pack/plugins/apm/server/lib/correlations/errors/get_overall_error_timeseries.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ProcessorEvent } from '../../../../common/processor_event'; +import { getBucketSize } from '../../helpers/get_bucket_size'; +import { + getTimeseriesAggregation, + getTransactionErrorRateTimeSeries, +} from '../../helpers/transaction_error_rate'; +import { withApmSpan } from '../../../utils/with_apm_span'; +import { CorrelationsOptions, getCorrelationsFilters } from '../get_filters'; + +export async function getOverallErrorTimeseries(options: CorrelationsOptions) { + return withApmSpan('get_error_rate_timeseries', async () => { + const { setup } = options; + const filters = getCorrelationsFilters(options); + const { start, end, apmEventClient } = setup; + const { intervalString } = getBucketSize({ start, end, numBuckets: 15 }); + + const params = { + // TODO: add support for metrics + apm: { events: [ProcessorEvent.transaction] }, + body: { + size: 0, + query: { bool: { filter: filters } }, + aggs: { + timeseries: getTimeseriesAggregation(start, end, intervalString), + }, + }, + }; + + const response = await apmEventClient.search(params); + const { aggregations } = response; + + if (!aggregations) { + return { overall: null }; + } + + return { + overall: { + timeseries: getTransactionErrorRateTimeSeries( + aggregations.timeseries.buckets + ), + }, + }; + }); +} diff --git a/x-pack/plugins/apm/server/lib/correlations/get_correlations_for_slow_transactions/get_latency_distribution.ts b/x-pack/plugins/apm/server/lib/correlations/get_correlations_for_slow_transactions/get_latency_distribution.ts deleted file mode 100644 index 88b1cf3a344ed..0000000000000 --- a/x-pack/plugins/apm/server/lib/correlations/get_correlations_for_slow_transactions/get_latency_distribution.ts +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { isEmpty, dropRightWhile } from 'lodash'; -import { AggregationOptionsByType } from '../../../../../../../typings/elasticsearch'; -import { ESFilter } from '../../../../../../../typings/elasticsearch'; -import { TRANSACTION_DURATION } from '../../../../common/elasticsearch_fieldnames'; -import { ProcessorEvent } from '../../../../common/processor_event'; -import { Setup, SetupTimeRange } from '../../helpers/setup_request'; -import { TopSigTerm } from '../process_significant_term_aggs'; -import { getMaxLatency } from './get_max_latency'; -import { withApmSpan } from '../../../utils/with_apm_span'; - -export async function getLatencyDistribution({ - setup, - backgroundFilters, - topSigTerms, -}: { - setup: Setup & SetupTimeRange; - backgroundFilters: ESFilter[]; - topSigTerms: TopSigTerm[]; -}) { - return withApmSpan('get_latency_distribution', async () => { - const { apmEventClient } = setup; - - if (isEmpty(topSigTerms)) { - return {}; - } - - const maxLatency = await getMaxLatency({ - setup, - backgroundFilters, - topSigTerms, - }); - - if (!maxLatency) { - return {}; - } - - const intervalBuckets = 15; - const distributionInterval = Math.floor(maxLatency / intervalBuckets); - - const distributionAgg = { - // filter out outliers not included in the significant term docs - filter: { range: { [TRANSACTION_DURATION]: { lte: maxLatency } } }, - aggs: { - dist_filtered_by_latency: { - histogram: { - // TODO: add support for metrics - field: TRANSACTION_DURATION, - interval: distributionInterval, - min_doc_count: 0, - extended_bounds: { - min: 0, - max: maxLatency, - }, - }, - }, - }, - }; - - const perTermAggs = topSigTerms.reduce( - (acc, term, index) => { - acc[`term_${index}`] = { - filter: { term: { [term.fieldName]: term.fieldValue } }, - aggs: { - distribution: distributionAgg, - }, - }; - return acc; - }, - {} as Record< - string, - { - filter: AggregationOptionsByType['filter']; - aggs: { - distribution: typeof distributionAgg; - }; - } - > - ); - - const params = { - // TODO: add support for metrics - apm: { events: [ProcessorEvent.transaction] }, - body: { - size: 0, - query: { bool: { filter: backgroundFilters } }, - aggs: { - // overall aggs - distribution: distributionAgg, - - // per term aggs - ...perTermAggs, - }, - }, - }; - - const response = await withApmSpan('get_terms_distribution', () => - apmEventClient.search(params) - ); - type Agg = NonNullable; - - if (!response.aggregations) { - return {}; - } - - function formatDistribution(distribution: Agg['distribution']) { - const total = distribution.doc_count; - - // remove trailing buckets that are empty and out of bounds of the desired number of buckets - const buckets = dropRightWhile( - distribution.dist_filtered_by_latency.buckets, - (bucket, index) => bucket.doc_count === 0 && index > intervalBuckets - 1 - ); - - return buckets.map((bucket) => ({ - x: bucket.key, - y: (bucket.doc_count / total) * 100, - })); - } - - return { - distributionInterval, - overall: { - distribution: formatDistribution(response.aggregations.distribution), - }, - significantTerms: topSigTerms.map((topSig, index) => { - // @ts-expect-error - const agg = response.aggregations[`term_${index}`] as Agg; - - return { - ...topSig, - distribution: formatDistribution(agg.distribution), - }; - }), - }; - }); -} diff --git a/x-pack/plugins/apm/server/lib/correlations/get_filters.ts b/x-pack/plugins/apm/server/lib/correlations/get_filters.ts new file mode 100644 index 0000000000000..92fc9c5d9622b --- /dev/null +++ b/x-pack/plugins/apm/server/lib/correlations/get_filters.ts @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Setup, SetupTimeRange } from '../helpers/setup_request'; +import { ESFilter } from '../../../../../../typings/elasticsearch'; +import { environmentQuery, rangeQuery, kqlQuery } from '../../utils/queries'; +import { + SERVICE_NAME, + TRANSACTION_NAME, + TRANSACTION_TYPE, + PROCESSOR_EVENT, +} from '../../../common/elasticsearch_fieldnames'; +import { ProcessorEvent } from '../../../common/processor_event'; + +export interface CorrelationsOptions { + setup: Setup & SetupTimeRange; + environment?: string; + kuery?: string; + serviceName: string | undefined; + transactionType: string | undefined; + transactionName: string | undefined; +} + +export function getCorrelationsFilters({ + setup, + environment, + kuery, + serviceName, + transactionType, + transactionName, +}: CorrelationsOptions) { + const { start, end } = setup; + const correlationsFilters: ESFilter[] = [ + { term: { [PROCESSOR_EVENT]: ProcessorEvent.transaction } }, + ...rangeQuery(start, end), + ...environmentQuery(environment), + ...kqlQuery(kuery), + ]; + + if (serviceName) { + correlationsFilters.push({ term: { [SERVICE_NAME]: serviceName } }); + } + + if (transactionType) { + correlationsFilters.push({ term: { [TRANSACTION_TYPE]: transactionType } }); + } + + if (transactionName) { + correlationsFilters.push({ term: { [TRANSACTION_NAME]: transactionName } }); + } + return correlationsFilters; +} diff --git a/x-pack/plugins/apm/server/lib/correlations/get_correlations_for_slow_transactions/index.ts b/x-pack/plugins/apm/server/lib/correlations/latency/get_correlations_for_slow_transactions.ts similarity index 63% rename from x-pack/plugins/apm/server/lib/correlations/get_correlations_for_slow_transactions/index.ts rename to x-pack/plugins/apm/server/lib/correlations/latency/get_correlations_for_slow_transactions.ts index 9472d385a26c6..0f93d1411a001 100644 --- a/x-pack/plugins/apm/server/lib/correlations/get_correlations_for_slow_transactions/index.ts +++ b/x-pack/plugins/apm/server/lib/correlations/latency/get_correlations_for_slow_transactions.ts @@ -6,75 +6,39 @@ */ import { AggregationOptionsByType } from '../../../../../../../typings/elasticsearch'; -import { ESFilter } from '../../../../../../../typings/elasticsearch'; -import { - environmentQuery, - rangeQuery, - kqlQuery, -} from '../../../../server/utils/queries'; -import { - SERVICE_NAME, - TRANSACTION_DURATION, - TRANSACTION_NAME, - TRANSACTION_TYPE, - PROCESSOR_EVENT, -} from '../../../../common/elasticsearch_fieldnames'; +import { TRANSACTION_DURATION } from '../../../../common/elasticsearch_fieldnames'; import { ProcessorEvent } from '../../../../common/processor_event'; -import { Setup, SetupTimeRange } from '../../helpers/setup_request'; import { getDurationForPercentile } from './get_duration_for_percentile'; import { processSignificantTermAggs } from '../process_significant_term_aggs'; import { getLatencyDistribution } from './get_latency_distribution'; import { withApmSpan } from '../../../utils/with_apm_span'; +import { CorrelationsOptions, getCorrelationsFilters } from '../get_filters'; -export async function getCorrelationsForSlowTransactions({ - environment, - kuery, - serviceName, - transactionType, - transactionName, - durationPercentile, - fieldNames, - setup, -}: { - environment?: string; - kuery?: string; - serviceName: string | undefined; - transactionType: string | undefined; - transactionName: string | undefined; +interface Options extends CorrelationsOptions { durationPercentile: number; fieldNames: string[]; - setup: Setup & SetupTimeRange; -}) { + maxLatency: number; + distributionInterval: number; +} +export async function getCorrelationsForSlowTransactions(options: Options) { return withApmSpan('get_correlations_for_slow_transactions', async () => { - const { start, end, apmEventClient } = setup; - - const backgroundFilters: ESFilter[] = [ - { term: { [PROCESSOR_EVENT]: ProcessorEvent.transaction } }, - ...rangeQuery(start, end), - ...environmentQuery(environment), - ...kqlQuery(kuery), - ]; - - if (serviceName) { - backgroundFilters.push({ term: { [SERVICE_NAME]: serviceName } }); - } - - if (transactionType) { - backgroundFilters.push({ term: { [TRANSACTION_TYPE]: transactionType } }); - } - - if (transactionName) { - backgroundFilters.push({ term: { [TRANSACTION_NAME]: transactionName } }); - } - + const { + durationPercentile, + fieldNames, + setup, + maxLatency, + distributionInterval, + } = options; + const { apmEventClient } = setup; + const filters = getCorrelationsFilters(options); const durationForPercentile = await getDurationForPercentile({ durationPercentile, - backgroundFilters, + filters, setup, }); if (!durationForPercentile) { - return {}; + return { significantTerms: [] }; } const response = await withApmSpan('get_significant_terms', () => { @@ -85,7 +49,7 @@ export async function getCorrelationsForSlowTransactions({ query: { bool: { // foreground filters - filter: backgroundFilters, + filter: filters, must: { function_score: { query: { @@ -112,7 +76,7 @@ export async function getCorrelationsForSlowTransactions({ background_filter: { bool: { filter: [ - ...backgroundFilters, + ...filters, { range: { [TRANSACTION_DURATION]: { @@ -132,17 +96,21 @@ export async function getCorrelationsForSlowTransactions({ return apmEventClient.search(params); }); if (!response.aggregations) { - return {}; + return { significantTerms: [] }; } const topSigTerms = processSignificantTermAggs({ sigTermAggs: response.aggregations, }); - return getLatencyDistribution({ + const significantTerms = await getLatencyDistribution({ setup, - backgroundFilters, + filters, topSigTerms, + maxLatency, + distributionInterval, }); + + return { significantTerms }; }); } diff --git a/x-pack/plugins/apm/server/lib/correlations/get_correlations_for_slow_transactions/get_duration_for_percentile.ts b/x-pack/plugins/apm/server/lib/correlations/latency/get_duration_for_percentile.ts similarity index 86% rename from x-pack/plugins/apm/server/lib/correlations/get_correlations_for_slow_transactions/get_duration_for_percentile.ts rename to x-pack/plugins/apm/server/lib/correlations/latency/get_duration_for_percentile.ts index 02141f5f9e76f..43c261743861d 100644 --- a/x-pack/plugins/apm/server/lib/correlations/get_correlations_for_slow_transactions/get_duration_for_percentile.ts +++ b/x-pack/plugins/apm/server/lib/correlations/latency/get_duration_for_percentile.ts @@ -13,11 +13,11 @@ import { Setup, SetupTimeRange } from '../../helpers/setup_request'; export async function getDurationForPercentile({ durationPercentile, - backgroundFilters, + filters, setup, }: { durationPercentile: number; - backgroundFilters: ESFilter[]; + filters: ESFilter[]; setup: Setup & SetupTimeRange; }) { return withApmSpan('get_duration_for_percentiles', async () => { @@ -29,7 +29,7 @@ export async function getDurationForPercentile({ body: { size: 0, query: { - bool: { filter: backgroundFilters }, + bool: { filter: filters }, }, aggs: { percentile: { @@ -42,6 +42,9 @@ export async function getDurationForPercentile({ }, }); - return Object.values(res.aggregations?.percentile.values || {})[0]; + const duration = Object.values( + res.aggregations?.percentile.values || {} + )[0]; + return duration || 0; }); } diff --git a/x-pack/plugins/apm/server/lib/correlations/latency/get_latency_distribution.ts b/x-pack/plugins/apm/server/lib/correlations/latency/get_latency_distribution.ts new file mode 100644 index 0000000000000..6d42b26b22e42 --- /dev/null +++ b/x-pack/plugins/apm/server/lib/correlations/latency/get_latency_distribution.ts @@ -0,0 +1,98 @@ +/* + * 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 { AggregationOptionsByType } from '../../../../../../../typings/elasticsearch'; +import { ESFilter } from '../../../../../../../typings/elasticsearch'; +import { ProcessorEvent } from '../../../../common/processor_event'; +import { Setup, SetupTimeRange } from '../../helpers/setup_request'; +import { TopSigTerm } from '../process_significant_term_aggs'; +import { withApmSpan } from '../../../utils/with_apm_span'; +import { + getDistributionAggregation, + trimBuckets, +} from './get_overall_latency_distribution'; + +export async function getLatencyDistribution({ + setup, + filters, + topSigTerms, + maxLatency, + distributionInterval, +}: { + setup: Setup & SetupTimeRange; + filters: ESFilter[]; + topSigTerms: TopSigTerm[]; + maxLatency: number; + distributionInterval: number; +}) { + return withApmSpan('get_latency_distribution', async () => { + const { apmEventClient } = setup; + + const distributionAgg = getDistributionAggregation( + maxLatency, + distributionInterval + ); + + const perTermAggs = topSigTerms.reduce( + (acc, term, index) => { + acc[`term_${index}`] = { + filter: { term: { [term.fieldName]: term.fieldValue } }, + aggs: { + distribution: distributionAgg, + }, + }; + return acc; + }, + {} as Record< + string, + { + filter: AggregationOptionsByType['filter']; + aggs: { + distribution: typeof distributionAgg; + }; + } + > + ); + + const params = { + // TODO: add support for metrics + apm: { events: [ProcessorEvent.transaction] }, + body: { + size: 0, + query: { bool: { filter: filters } }, + aggs: perTermAggs, + }, + }; + + const response = await withApmSpan('get_terms_distribution', () => + apmEventClient.search(params) + ); + type Agg = NonNullable; + + if (!response.aggregations) { + return []; + } + + return topSigTerms.map((topSig, index) => { + // ignore the typescript error since existence of response.aggregations is already checked: + // @ts-expect-error + const agg = response.aggregations[`term_${index}`] as Agg[string]; + const total = agg.distribution.doc_count; + const buckets = trimBuckets( + agg.distribution.dist_filtered_by_latency.buckets + ); + + return { + ...topSig, + distribution: buckets.map((bucket) => ({ + x: bucket.key, + y: (bucket.doc_count / total) * 100, + })), + }; + }); + }); +} diff --git a/x-pack/plugins/apm/server/lib/correlations/get_correlations_for_slow_transactions/get_max_latency.ts b/x-pack/plugins/apm/server/lib/correlations/latency/get_max_latency.ts similarity index 76% rename from x-pack/plugins/apm/server/lib/correlations/get_correlations_for_slow_transactions/get_max_latency.ts rename to x-pack/plugins/apm/server/lib/correlations/latency/get_max_latency.ts index 5f12c86a9c70c..8b415bf0d80a7 100644 --- a/x-pack/plugins/apm/server/lib/correlations/get_correlations_for_slow_transactions/get_max_latency.ts +++ b/x-pack/plugins/apm/server/lib/correlations/latency/get_max_latency.ts @@ -14,12 +14,12 @@ import { TopSigTerm } from '../process_significant_term_aggs'; export async function getMaxLatency({ setup, - backgroundFilters, - topSigTerms, + filters, + topSigTerms = [], }: { setup: Setup & SetupTimeRange; - backgroundFilters: ESFilter[]; - topSigTerms: TopSigTerm[]; + filters: ESFilter[]; + topSigTerms?: TopSigTerm[]; }) { return withApmSpan('get_max_latency', async () => { const { apmEventClient } = setup; @@ -31,13 +31,17 @@ export async function getMaxLatency({ size: 0, query: { bool: { - filter: backgroundFilters, + filter: filters, - // only include docs containing the significant terms - should: topSigTerms.map((term) => ({ - term: { [term.fieldName]: term.fieldValue }, - })), - minimum_should_match: 1, + ...(topSigTerms.length + ? { + // only include docs containing the significant terms + should: topSigTerms.map((term) => ({ + term: { [term.fieldName]: term.fieldValue }, + })), + minimum_should_match: 1, + } + : null), }, }, aggs: { diff --git a/x-pack/plugins/apm/server/lib/correlations/latency/get_overall_latency_distribution.ts b/x-pack/plugins/apm/server/lib/correlations/latency/get_overall_latency_distribution.ts new file mode 100644 index 0000000000000..c5d4def51ea54 --- /dev/null +++ b/x-pack/plugins/apm/server/lib/correlations/latency/get_overall_latency_distribution.ts @@ -0,0 +1,107 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { dropRightWhile } from 'lodash'; +import { TRANSACTION_DURATION } from '../../../../common/elasticsearch_fieldnames'; +import { ProcessorEvent } from '../../../../common/processor_event'; +import { getMaxLatency } from './get_max_latency'; +import { withApmSpan } from '../../../utils/with_apm_span'; +import { CorrelationsOptions, getCorrelationsFilters } from '../get_filters'; + +export const INTERVAL_BUCKETS = 15; + +export function getDistributionAggregation( + maxLatency: number, + distributionInterval: number +) { + return { + filter: { range: { [TRANSACTION_DURATION]: { lte: maxLatency } } }, + aggs: { + dist_filtered_by_latency: { + histogram: { + // TODO: add support for metrics + field: TRANSACTION_DURATION, + interval: distributionInterval, + min_doc_count: 0, + extended_bounds: { + min: 0, + max: maxLatency, + }, + }, + }, + }, + }; +} + +export async function getOverallLatencyDistribution( + options: CorrelationsOptions +) { + const { setup } = options; + const filters = getCorrelationsFilters(options); + + return withApmSpan('get_overall_latency_distribution', async () => { + const { apmEventClient } = setup; + const maxLatency = await getMaxLatency({ setup, filters }); + if (!maxLatency) { + return { + maxLatency: null, + distributionInterval: null, + overallDistribution: null, + }; + } + const distributionInterval = Math.floor(maxLatency / INTERVAL_BUCKETS); + + const params = { + // TODO: add support for metrics + apm: { events: [ProcessorEvent.transaction] }, + body: { + size: 0, + query: { bool: { filter: filters } }, + aggs: { + // overall distribution agg + distribution: getDistributionAggregation( + maxLatency, + distributionInterval + ), + }, + }, + }; + + const response = await withApmSpan('get_terms_distribution', () => + apmEventClient.search(params) + ); + + if (!response.aggregations) { + return { + maxLatency, + distributionInterval, + overallDistribution: null, + }; + } + + const { distribution } = response.aggregations; + const total = distribution.doc_count; + const buckets = trimBuckets(distribution.dist_filtered_by_latency.buckets); + + return { + maxLatency, + distributionInterval, + overallDistribution: buckets.map((bucket) => ({ + x: bucket.key, + y: (bucket.doc_count / total) * 100, + })), + }; + }); +} + +// remove trailing buckets that are empty and out of bounds of the desired number of buckets +export function trimBuckets(buckets: T[]) { + return dropRightWhile( + buckets, + (bucket, index) => bucket.doc_count === 0 && index > INTERVAL_BUCKETS - 1 + ); +} diff --git a/x-pack/plugins/apm/server/lib/helpers/transaction_error_rate.ts b/x-pack/plugins/apm/server/lib/helpers/transaction_error_rate.ts index 11d65b7697e9a..b60a2a071e6dc 100644 --- a/x-pack/plugins/apm/server/lib/helpers/transaction_error_rate.ts +++ b/x-pack/plugins/apm/server/lib/helpers/transaction_error_rate.ts @@ -21,6 +21,20 @@ export const getOutcomeAggregation = () => ({ type OutcomeAggregation = ReturnType; +export const getTimeseriesAggregation = ( + start: number, + end: number, + intervalString: string +) => ({ + date_histogram: { + field: '@timestamp', + fixed_interval: intervalString, + min_doc_count: 0, + extended_bounds: { min: start, max: end }, + }, + aggs: { outcomes: getOutcomeAggregation() }, +}); + export function calculateTransactionErrorPercentage( outcomeResponse: AggregationResultOf ) { diff --git a/x-pack/plugins/apm/server/routes/correlations.ts b/x-pack/plugins/apm/server/routes/correlations.ts index 48305d1a9df07..c7c69e0774822 100644 --- a/x-pack/plugins/apm/server/routes/correlations.ts +++ b/x-pack/plugins/apm/server/routes/correlations.ts @@ -9,8 +9,10 @@ import Boom from '@hapi/boom'; import { i18n } from '@kbn/i18n'; import * as t from 'io-ts'; import { isActivePlatinumLicense } from '../../common/license_check'; -import { getCorrelationsForFailedTransactions } from '../lib/correlations/get_correlations_for_failed_transactions'; -import { getCorrelationsForSlowTransactions } from '../lib/correlations/get_correlations_for_slow_transactions'; +import { getCorrelationsForFailedTransactions } from '../lib/correlations/errors/get_correlations_for_failed_transactions'; +import { getOverallErrorTimeseries } from '../lib/correlations/errors/get_overall_error_timeseries'; +import { getCorrelationsForSlowTransactions } from '../lib/correlations/latency/get_correlations_for_slow_transactions'; +import { getOverallLatencyDistribution } from '../lib/correlations/latency/get_overall_latency_distribution'; import { setupRequest } from '../lib/helpers/setup_request'; import { createRoute } from './create_route'; import { environmentRt, kueryRt, rangeRt } from './default_api_types'; @@ -23,8 +25,47 @@ const INVALID_LICENSE = i18n.translate( } ); +export const correlationsLatencyDistributionRoute = createRoute({ + endpoint: 'GET /api/apm/correlations/latency/overall_distribution', + params: t.type({ + query: t.intersection([ + t.partial({ + serviceName: t.string, + transactionName: t.string, + transactionType: t.string, + }), + environmentRt, + kueryRt, + rangeRt, + ]), + }), + options: { tags: ['access:apm'] }, + handler: async ({ context, request }) => { + if (!isActivePlatinumLicense(context.licensing.license)) { + throw Boom.forbidden(INVALID_LICENSE); + } + const setup = await setupRequest(context, request); + const { + environment, + kuery, + serviceName, + transactionType, + transactionName, + } = context.params.query; + + return getOverallLatencyDistribution({ + environment, + kuery, + serviceName, + transactionType, + transactionName, + setup, + }); + }, +}); + export const correlationsForSlowTransactionsRoute = createRoute({ - endpoint: 'GET /api/apm/correlations/slow_transactions', + endpoint: 'GET /api/apm/correlations/latency/slow_transactions', params: t.type({ query: t.intersection([ t.partial({ @@ -35,6 +76,8 @@ export const correlationsForSlowTransactionsRoute = createRoute({ t.type({ durationPercentile: t.string, fieldNames: t.string, + maxLatency: t.string, + distributionInterval: t.string, }), environmentRt, kueryRt, @@ -55,6 +98,8 @@ export const correlationsForSlowTransactionsRoute = createRoute({ transactionName, durationPercentile, fieldNames, + maxLatency, + distributionInterval, } = context.params.query; return getCorrelationsForSlowTransactions({ @@ -66,12 +111,53 @@ export const correlationsForSlowTransactionsRoute = createRoute({ durationPercentile: parseInt(durationPercentile, 10), fieldNames: fieldNames.split(','), setup, + maxLatency: parseInt(maxLatency, 10), + distributionInterval: parseInt(distributionInterval, 10), + }); + }, +}); + +export const correlationsErrorDistributionRoute = createRoute({ + endpoint: 'GET /api/apm/correlations/errors/overall_timeseries', + params: t.type({ + query: t.intersection([ + t.partial({ + serviceName: t.string, + transactionName: t.string, + transactionType: t.string, + }), + environmentRt, + kueryRt, + rangeRt, + ]), + }), + options: { tags: ['access:apm'] }, + handler: async ({ context, request }) => { + if (!isActivePlatinumLicense(context.licensing.license)) { + throw Boom.forbidden(INVALID_LICENSE); + } + const setup = await setupRequest(context, request); + const { + environment, + kuery, + serviceName, + transactionType, + transactionName, + } = context.params.query; + + return getOverallErrorTimeseries({ + environment, + kuery, + serviceName, + transactionType, + transactionName, + setup, }); }, }); export const correlationsForFailedTransactionsRoute = createRoute({ - endpoint: 'GET /api/apm/correlations/failed_transactions', + endpoint: 'GET /api/apm/correlations/errors/failed_transactions', params: t.type({ query: t.intersection([ t.partial({ diff --git a/x-pack/plugins/apm/server/routes/create_apm_api.ts b/x-pack/plugins/apm/server/routes/create_apm_api.ts index 2b5fb0b516ab5..5b74aa4347f14 100644 --- a/x-pack/plugins/apm/server/routes/create_apm_api.ts +++ b/x-pack/plugins/apm/server/routes/create_apm_api.ts @@ -58,7 +58,9 @@ import { rootTransactionByTraceIdRoute, } from './traces'; import { + correlationsLatencyDistributionRoute, correlationsForSlowTransactionsRoute, + correlationsErrorDistributionRoute, correlationsForFailedTransactionsRoute, } from './correlations'; import { @@ -152,7 +154,9 @@ const createApmApi = () => { .add(createOrUpdateAgentConfigurationRoute) // Correlations + .add(correlationsLatencyDistributionRoute) .add(correlationsForSlowTransactionsRoute) + .add(correlationsErrorDistributionRoute) .add(correlationsForFailedTransactionsRoute) // APM indices diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/analytics.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/analytics.tsx index 82b0d9a318f1d..0eef9b0c688c0 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/analytics.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/analytics.tsx @@ -83,8 +83,7 @@ export const Analytics: React.FC = () => { /> - {/* TODO: Update this panel to use the bordered version once available */} - + { {RECENT_QUERIES}} subtitle={i18n.translate( 'xpack.enterpriseSearch.appSearch.engine.analytics.recentQueriesDescription', diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/query_detail.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/query_detail.tsx index f00c4e29a7190..83c83aa36f1bb 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/query_detail.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/query_detail.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { useValues } from 'kea'; -import { EuiSpacer } from '@elastic/eui'; +import { EuiPanel, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { SetAppSearchChrome as SetPageChrome } from '../../../../shared/kibana_chrome'; @@ -56,17 +56,19 @@ export const QueryDetail: React.FC = ({ breadcrumbs }) => { /> - + + + { {shouldShowCredentialsForm && } - +

{i18n.translate('xpack.enterpriseSearch.appSearch.credentials.apiEndpoint', { @@ -116,7 +116,9 @@ export const Credentials: React.FC = () => { - {!!dataLoading ? : } + + {!!dataLoading ? : } + ); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_engine_access.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_engine_access.tsx index 0d6ebfe437927..7e40eb63338bb 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_engine_access.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_engine_access.tsx @@ -104,7 +104,7 @@ export const EngineSelection: React.FC = () => { return ( <> - +

{i18n.translate( diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_read_write_access.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_read_write_access.tsx index 0b631089c3984..f363f6978db29 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_read_write_access.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_read_write_access.tsx @@ -22,7 +22,7 @@ export const FormKeyReadWriteAccess: React.FC = () => { return ( <> - +

{i18n.translate('xpack.enterpriseSearch.appSearch.credentials.formReadWrite.label', { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_creation.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_creation.tsx index b1bfc6c2ab7fa..10f1fc093e60f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_creation.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_creation.tsx @@ -25,7 +25,7 @@ export const CurationCreation: React.FC = () => { <> - +

{i18n.translate( diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations.tsx index a523a683c4f5b..624790c847167 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations.tsx @@ -54,7 +54,7 @@ export const Curations: React.FC = () => { , ]} /> - + diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/data_panel/data_panel.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/data_panel/data_panel.test.tsx index c111383816e36..8034b72d885da 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/data_panel/data_panel.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/data_panel/data_panel.test.tsx @@ -94,6 +94,14 @@ describe('DataPanel', () => { expect(wrapper.find(LoadingOverlay)).toHaveLength(1); }); + it('passes hasBorder', () => { + const wrapper = shallow(Test

} />); + expect(wrapper.prop('hasBorder')).toBeFalsy(); + + wrapper.setProps({ hasBorder: true }); + expect(wrapper.prop('hasBorder')).toBeTruthy(); + }); + it('passes class names', () => { const wrapper = shallow(Test

} className="testing" />); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/data_panel/data_panel.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/data_panel/data_panel.tsx index 825311fa1652a..ce878dc3cf29a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/data_panel/data_panel.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/data_panel/data_panel.tsx @@ -29,6 +29,7 @@ interface Props { iconType?: string; action?: React.ReactNode; filled?: boolean; + hasBorder?: boolean; isLoading?: boolean; className?: string; } @@ -39,6 +40,7 @@ export const DataPanel: React.FC = ({ iconType, action, filled, + hasBorder, isLoading, className, children, @@ -52,6 +54,7 @@ export const DataPanel: React.FC = ({ {

- + POST diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/document_detail.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/document_detail.tsx index dd55c26b5b298..fefe983df3342 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/document_detail.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/document_detail.tsx @@ -91,7 +91,7 @@ export const DocumentDetail: React.FC = ({ engineBreadcrumb }) => { , ]} /> - + diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_creation/engine_creation.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_creation/engine_creation.tsx index bab31d0fccc40..4e1d7bc3e8e48 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_creation/engine_creation.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_creation/engine_creation.tsx @@ -16,12 +16,11 @@ import { EuiFlexItem, EuiFieldText, EuiSelect, - EuiPageBody, EuiPageHeader, + EuiPageContent, EuiSpacer, EuiTitle, EuiButton, - EuiPanel, } from '@elastic/eui'; import { FlashMessages } from '../../../shared/flash_messages'; @@ -48,75 +47,73 @@ export const EngineCreation: React.FC = () => {
- - - - -
{ - e.preventDefault(); - submitEngine(); - }} - > - -

{ENGINE_CREATION_FORM_TITLE}

-
- - - - 0 && rawName !== name ? ( - <> - {SANITIZED_NAME_NOTE} {name} - - ) : ( - ALLOWED_CHARS_NOTE - ) - } + + + + { + e.preventDefault(); + submitEngine(); + }} + > + +

{ENGINE_CREATION_FORM_TITLE}

+
+ + + + 0 && rawName !== name ? ( + <> + {SANITIZED_NAME_NOTE} {name} + + ) : ( + ALLOWED_CHARS_NOTE + ) + } + fullWidth + > + setRawName(event.currentTarget.value)} + autoComplete="off" fullWidth - > - setRawName(event.currentTarget.value)} - autoComplete="off" - fullWidth - data-test-subj="EngineCreationNameInput" - placeholder={ENGINE_CREATION_FORM_ENGINE_NAME_PLACEHOLDER} - autoFocus - /> - - - - - setLanguage(event.currentTarget.value)} - /> - - - - - - {ENGINE_CREATION_FORM_SUBMIT_BUTTON_LABEL} - - -
-
-
+ data-test-subj="EngineCreationNameInput" + placeholder={ENGINE_CREATION_FORM_ENGINE_NAME_PLACEHOLDER} + autoFocus + /> + + + + + setLanguage(event.currentTarget.value)} + /> + + + + + + {ENGINE_CREATION_FORM_SUBMIT_BUTTON_LABEL} + + + +
); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/recent_api_logs.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/recent_api_logs.tsx index e77a4ad7b0348..1a8e4703d7c2e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/recent_api_logs.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/recent_api_logs.tsx @@ -34,6 +34,7 @@ export const RecentApiLogs: React.FC = () => { {VIEW_API_LOGS} } + hasBorder > TODO: API Logs Table {/* */} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/total_charts.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/total_charts.tsx index 77ba9ad0f9514..136c9c6603e5c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/total_charts.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/total_charts.tsx @@ -40,6 +40,7 @@ export const TotalCharts: React.FC = () => { {VIEW_ANALYTICS} } + hasBorder > { {VIEW_API_LOGS} } + hasBorder > { <> - + {canManageEngines ? ( { <> - + diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_overview.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_overview.tsx index baf275fbe6c2c..a09f30035bafc 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_overview.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_overview.tsx @@ -84,7 +84,7 @@ export const EnginesOverview: React.FC = () => { - + diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/error_connecting/error_connecting.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/error_connecting/error_connecting.tsx index d7fde0cd5dd25..b193e00c1d48d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/error_connecting/error_connecting.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/error_connecting/error_connecting.tsx @@ -19,7 +19,7 @@ export const ErrorConnecting: React.FC = () => { - + diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/library/library.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/library/library.tsx index 5e268cc0fd214..ad693628d911e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/library/library.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/library/library.tsx @@ -86,7 +86,7 @@ export const Library: React.FC = () => { <> - +

Result

diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/meta_engine_creation/meta_engine_creation.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/meta_engine_creation/meta_engine_creation.tsx index a3dbf7259975b..85c24f1e42368 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/meta_engine_creation/meta_engine_creation.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/meta_engine_creation/meta_engine_creation.tsx @@ -87,7 +87,7 @@ export const MetaEngineCreation: React.FC = () => { } /> - + = ({ boost, index, name }) => { }; return ( - + + {getBoostForm()} - + = ({ name, type, boosts = [] }) => { ); return ( - + diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_form.scss b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_form.scss index 9795564da04d5..065effef9dded 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_form.scss +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_form.scss @@ -19,7 +19,6 @@ } .relevanceTuningAccordionItem { - border: none; border-top: $euiBorderThin; border-radius: 0; } diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_form.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_form.tsx index ab72f29a678c9..39200a699b3f7 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_form.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_form.tsx @@ -73,7 +73,7 @@ export const RelevanceTuningForm: React.FC = () => { )} {filteredSchemaFields.map((fieldName) => ( - + { {filteredSchemaFieldsWithConflicts.map((fieldName) => ( - +

{fieldName}

diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_preview.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_preview.tsx index 298b692ac7b80..5e5ee2ea8d0f0 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_preview.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_preview.tsx @@ -48,7 +48,7 @@ export const RelevanceTuningPreview: React.FC = () => { const { engineName, isMetaEngine } = useValues(EngineLogic); return ( - +

{i18n.translate('xpack.enterpriseSearch.appSearch.engine.relevanceTuning.preview.title', { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mapping.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mapping.tsx index bfa3fefb2732d..ebd034caaedb3 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mapping.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mapping.tsx @@ -166,7 +166,7 @@ export const RoleMapping: React.FC = ({ isNew }) => { - +

{ROLE_TITLE}

@@ -175,7 +175,6 @@ export const RoleMapping: React.FC = ({ isNew }) => {

{FULL_ENGINE_ACCESS_TITLE}

- export{' '} {STANDARD_ROLE_TYPES.map(({ type, description }) => ( = ({ isNew }) => {
{hasAdvancedRoles && ( - +

{ENGINE_ACCESS_TITLE}

diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings.tsx index e31f5c04bdb45..2ec2b93d1e24f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings.tsx @@ -127,7 +127,7 @@ export const RoleMappings: React.FC = () => { pageTitle={ROLE_MAPPINGS_TITLE} description={ROLE_MAPPINGS_DESCRIPTION} /> - + {roleMappings.length === 0 ? roleMappingEmptyState : roleMappingsTable} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/sample_engine_creation_cta/sample_engine_creation_cta.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/sample_engine_creation_cta/sample_engine_creation_cta.tsx index 8de6b6030ef66..6f1ccd1ae2b53 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/sample_engine_creation_cta/sample_engine_creation_cta.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/sample_engine_creation_cta/sample_engine_creation_cta.tsx @@ -23,9 +23,9 @@ export const SampleEngineCreationCta: React.FC = () => { const { createSampleEngine } = useActions(SampleEngineCreationCtaLogic); return ( - - - + + +

{SAMPLE_ENGINE_CREATION_CTA_TITLE}

diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/settings.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/settings.tsx index 88b62b8ae83f7..2d5dd08f81288 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/settings.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/settings.tsx @@ -21,7 +21,7 @@ export const Settings: React.FC = () => { <> - + diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found.tsx index 5699568c40558..f288961b72de4 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found.tsx @@ -73,7 +73,7 @@ export const NotFound: React.FC = ({ product = {}, breadcrumbs }) - + } body={ diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/attribute_selector.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/attribute_selector.test.tsx index b5d1ebb899ba1..504acf9ae1c6a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/attribute_selector.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/attribute_selector.test.tsx @@ -41,14 +41,6 @@ describe('AttributeSelector', () => { expect(wrapper.find('[data-test-subj="AttributeSelector"]').exists()).toBe(true); }); - it('renders disabled panel with className', () => { - const wrapper = shallow(); - - expect(wrapper.find('[data-test-subj="AttributeSelector"]').prop('className')).toEqual( - 'euiPanel--disabled' - ); - }); - describe('Auth Providers', () => { const findAuthProvidersSelect = (wrapper: ShallowWrapper) => wrapper.find('[data-test-subj="AuthProviderSelect"]'); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/attribute_selector.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/attribute_selector.tsx index 48d1447e9bd0f..0417331be208d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/attribute_selector.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/attribute_selector.tsx @@ -100,11 +100,7 @@ export const AttributeSelector: React.FC = ({ handleAuthProviderChange = () => null, }) => { return ( - +

{ATTRIBUTE_SELECTOR_TITLE}

diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mapping.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mapping.tsx index cf402f4525f9e..7db1e82d29449 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mapping.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mapping.tsx @@ -141,7 +141,7 @@ export const RoleMapping: React.FC = ({ isNew }) => { - +

{ROLE_LABEL}

@@ -158,7 +158,7 @@ export const RoleMapping: React.FC = ({ isNew }) => {
- +

{GROUP_ASSIGNMENT_TITLE}

diff --git a/x-pack/plugins/fleet/server/services/artifacts/artifacts.test.ts b/x-pack/plugins/fleet/server/services/artifacts/artifacts.test.ts index 20e80a2f997af..d4f129a1ae241 100644 --- a/x-pack/plugins/fleet/server/services/artifacts/artifacts.test.ts +++ b/x-pack/plugins/fleet/server/services/artifacts/artifacts.test.ts @@ -9,6 +9,8 @@ import { elasticsearchServiceMock } from 'src/core/server/mocks'; import { ResponseError } from '@elastic/elasticsearch/lib/errors'; +import type { ApiResponse } from '@elastic/elasticsearch/lib/Transport'; + import { FLEET_SERVER_ARTIFACTS_INDEX } from '../../../common'; import { ArtifactsElasticsearchError } from '../../errors'; @@ -100,6 +102,14 @@ describe('When using the artifacts services', () => { }); }); + it('should ignore 409 errors from elasticsearch', async () => { + const error = new ResponseError({ statusCode: 409 } as ApiResponse); + // Unclear why `mockRejectedValue()` has the params value type set to `never` + // @ts-expect-error + esClientMock.create.mockRejectedValue(error); + await expect(() => createArtifact(esClientMock, newArtifact)).not.toThrow(); + }); + it('should throw an ArtifactElasticsearchError if one is encountered', async () => { setEsClientMethodResponseToError(esClientMock, 'create'); await expect(createArtifact(esClientMock, newArtifact)).rejects.toBeInstanceOf( diff --git a/x-pack/plugins/fleet/server/services/epm/agent/agent.test.ts b/x-pack/plugins/fleet/server/services/epm/agent/agent.test.ts index dde9f1733dfe3..bc4ffffb68358 100644 --- a/x-pack/plugins/fleet/server/services/epm/agent/agent.test.ts +++ b/x-pack/plugins/fleet/server/services/epm/agent/agent.test.ts @@ -65,6 +65,12 @@ custom: {{ custom }} {{#if key.patterns}} key.patterns: {{key.patterns}} {{/if}} +{{#if emptyfield}} +emptyfield: {{emptyfield}} +{{/if}} +{{#if nullfield}} +nullfield: {{nullfield}} +{{/if}} {{ testEmpty }} `; const vars = { @@ -82,6 +88,8 @@ foo: bar `, }, password: { type: 'password', value: '' }, + emptyfield: { type: 'yaml', value: '' }, + nullfield: { type: 'yaml' }, }; const output = compileTemplate(vars, streamTemplate); diff --git a/x-pack/plugins/fleet/server/services/epm/agent/agent.ts b/x-pack/plugins/fleet/server/services/epm/agent/agent.ts index 26e1497e93852..84a8ab581354a 100644 --- a/x-pack/plugins/fleet/server/services/epm/agent/agent.ts +++ b/x-pack/plugins/fleet/server/services/epm/agent/agent.ts @@ -90,7 +90,7 @@ function buildTemplateVariables(variables: PackagePolicyConfigRecord, templateSt if (recordEntry.type && recordEntry.type === 'yaml') { const yamlKeyPlaceholder = `##${key}##`; - varPart[lastKeyPart] = `"${yamlKeyPlaceholder}"`; + varPart[lastKeyPart] = recordEntry.value ? `"${yamlKeyPlaceholder}"` : null; yamlValues[yamlKeyPlaceholder] = recordEntry.value ? safeLoad(recordEntry.value) : null; } else if ( recordEntry.type && diff --git a/x-pack/plugins/fleet/server/services/fleet_server/saved_object_migrations.ts b/x-pack/plugins/fleet/server/services/fleet_server/saved_object_migrations.ts index f078b214e4dfd..7af2b791f3707 100644 --- a/x-pack/plugins/fleet/server/services/fleet_server/saved_object_migrations.ts +++ b/x-pack/plugins/fleet/server/services/fleet_server/saved_object_migrations.ts @@ -25,6 +25,7 @@ import { listEnrollmentApiKeys, getEnrollmentAPIKey } from '../api_keys/enrollme import { appContextService } from '../app_context'; import { isAgentsSetup } from '../agents'; import { agentPolicyService } from '../agent_policy'; +import { invalidateAPIKeys } from '../api_keys'; export async function runFleetServerMigration() { // If Agents are not setup skip as there is nothing to migrate @@ -56,6 +57,7 @@ function getInternalUserSOClient() { async function migrateAgents() { const esClient = appContextService.getInternalUserESClient(); const soClient = getInternalUserSOClient(); + const logger = appContextService.getLogger(); let hasMore = true; while (hasMore) { const res = await soClient.find({ @@ -75,11 +77,20 @@ async function migrateAgents() { .getEncryptedSavedObjects() .getDecryptedAsInternalUser(AGENT_SAVED_OBJECT_TYPE, so.id); + await invalidateAPIKeys( + soClient, + [attributes.access_api_key_id, attributes.default_api_key_id].filter( + (keyId): keyId is string => keyId !== undefined + ) + ).catch((error) => { + logger.error(`Invalidating API keys for agent ${so.id} failed: ${error.message}`); + }); + const body: FleetServerAgent = { type: attributes.type, - active: attributes.active, + active: false, enrolled_at: attributes.enrolled_at, - unenrolled_at: attributes.unenrolled_at, + unenrolled_at: new Date().toISOString(), unenrollment_started_at: attributes.unenrollment_started_at, upgraded_at: attributes.upgraded_at, upgrade_started_at: attributes.upgrade_started_at, diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/table.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/table.tsx index f22e826a877ec..90cb48df4b8d9 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/table.tsx +++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/table.tsx @@ -100,7 +100,7 @@ export const ComponentTable: FunctionComponent = ({ {...reactRouterNavigate(history, '/create_component_template')} > {i18n.translate('xpack.idxMgmt.componentTemplatesList.table.createButtonLabel', { - defaultMessage: 'Create a component template', + defaultMessage: 'Create component template', })} , ], diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/table.tsx b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/table.tsx index 95d4a8a653dc1..9cf807052fcb9 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/table.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/table.tsx @@ -91,7 +91,7 @@ export const PipelineTable: FunctionComponent = ({ {...reactRouterNavigate(history, '/create')} > {i18n.translate('xpack.ingestPipelines.list.table.createPipelineButtonLabel', { - defaultMessage: 'Create a pipeline', + defaultMessage: 'Create pipeline', })} , ], diff --git a/x-pack/plugins/maps/common/constants.ts b/x-pack/plugins/maps/common/constants.ts index ecdf94a076809..7152d76afbdbe 100644 --- a/x-pack/plugins/maps/common/constants.ts +++ b/x-pack/plugins/maps/common/constants.ts @@ -300,3 +300,7 @@ export type FieldFormatter = (value: RawValue) => string | number; export const INDEX_META_DATA_CREATED_BY = 'maps-drawing-data-ingest'; export const MAX_DRAWING_SIZE_BYTES = 10485760; // 10MB + +export const emsWorldLayerId = 'world_countries'; +export const emsRegionLayerId = 'administrative_regions_lvl2'; +export const emsUsaZipLayerId = 'usa_zip_codes'; diff --git a/x-pack/plugins/maps/common/descriptor_types/data_request_descriptor_types.ts b/x-pack/plugins/maps/common/descriptor_types/data_request_descriptor_types.ts index dd01b7b596c30..e1f682678df4b 100644 --- a/x-pack/plugins/maps/common/descriptor_types/data_request_descriptor_types.ts +++ b/x-pack/plugins/maps/common/descriptor_types/data_request_descriptor_types.ts @@ -26,6 +26,7 @@ export type MapFilters = { }; type ESSearchSourceSyncMeta = { + filterByMapBounds: boolean; sortField: string; sortOrder: SortDirection; scalingType: SCALING_TYPES; diff --git a/x-pack/plugins/maps/common/descriptor_types/source_descriptor_types.ts b/x-pack/plugins/maps/common/descriptor_types/source_descriptor_types.ts index 7b757aa9cf10b..9c4ef3fde6d16 100644 --- a/x-pack/plugins/maps/common/descriptor_types/source_descriptor_types.ts +++ b/x-pack/plugins/maps/common/descriptor_types/source_descriptor_types.ts @@ -95,8 +95,8 @@ export type ESGeoLineSourceDescriptor = AbstractESAggSourceDescriptor & { export type ESSearchSourceDescriptor = AbstractESSourceDescriptor & { geoField: string; - filterByMapBounds?: boolean; - tooltipProperties?: string[]; + filterByMapBounds: boolean; + tooltipProperties: string[]; sortField: string; sortOrder: SortDirection; scalingType: SCALING_TYPES; diff --git a/x-pack/plugins/maps/public/api/ems.ts b/x-pack/plugins/maps/public/api/ems.ts new file mode 100644 index 0000000000000..da6e88c84e22c --- /dev/null +++ b/x-pack/plugins/maps/public/api/ems.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EMSTermJoinConfig, SampleValuesConfig } from '../ems_autosuggest'; +import { lazyLoadMapModules } from '../lazy_load_bundle'; + +export async function suggestEMSTermJoinConfig( + sampleValuesConfig: SampleValuesConfig +): Promise { + const mapModules = await lazyLoadMapModules(); + return await mapModules.suggestEMSTermJoinConfig(sampleValuesConfig); +} diff --git a/x-pack/plugins/maps/public/api/index.ts b/x-pack/plugins/maps/public/api/index.ts index 6aac4f6410e6d..186fd98c90bf6 100644 --- a/x-pack/plugins/maps/public/api/index.ts +++ b/x-pack/plugins/maps/public/api/index.ts @@ -8,3 +8,4 @@ export { MapsStartApi } from './start_api'; export { createLayerDescriptors } from './create_layer_descriptors'; export { registerLayerWizard, registerSource } from './register'; +export { suggestEMSTermJoinConfig } from './ems'; diff --git a/x-pack/plugins/maps/public/api/register.ts b/x-pack/plugins/maps/public/api/register.ts index 2be928585a936..037eeadc48df7 100644 --- a/x-pack/plugins/maps/public/api/register.ts +++ b/x-pack/plugins/maps/public/api/register.ts @@ -5,8 +5,8 @@ * 2.0. */ -import { SourceRegistryEntry } from '../classes/sources/source_registry'; -import { LayerWizard } from '../classes/layers/layer_wizard_registry'; +import type { SourceRegistryEntry } from '../classes/sources/source_registry'; +import type { LayerWizard } from '../classes/layers/layer_wizard_registry'; import { lazyLoadMapModules } from '../lazy_load_bundle'; export async function registerLayerWizard(layerWizard: LayerWizard): Promise { diff --git a/x-pack/plugins/maps/public/api/start_api.ts b/x-pack/plugins/maps/public/api/start_api.ts index 3005f6522df0d..e4213fe07a49c 100644 --- a/x-pack/plugins/maps/public/api/start_api.ts +++ b/x-pack/plugins/maps/public/api/start_api.ts @@ -5,10 +5,11 @@ * 2.0. */ -import { LayerDescriptor } from '../../common/descriptor_types'; -import { SourceRegistryEntry } from '../classes/sources/source_registry'; -import { LayerWizard } from '../classes/layers/layer_wizard_registry'; +import type { LayerDescriptor } from '../../common/descriptor_types'; +import type { SourceRegistryEntry } from '../classes/sources/source_registry'; +import type { LayerWizard } from '../classes/layers/layer_wizard_registry'; import type { CreateLayerDescriptorParams } from '../classes/sources/es_search_source'; +import type { SampleValuesConfig, EMSTermJoinConfig } from '../ems_autosuggest'; export interface MapsStartApi { createLayerDescriptors: { @@ -23,4 +24,5 @@ export interface MapsStartApi { }; registerLayerWizard(layerWizard: LayerWizard): Promise; registerSource(entry: SourceRegistryEntry): Promise; + suggestEMSTermJoinConfig(config: SampleValuesConfig): Promise; } diff --git a/x-pack/plugins/maps/public/classes/layers/choropleth_layer_wizard/layer_template.tsx b/x-pack/plugins/maps/public/classes/layers/choropleth_layer_wizard/layer_template.tsx index 909cb6909ee56..82a741e7ccdab 100644 --- a/x-pack/plugins/maps/public/classes/layers/choropleth_layer_wizard/layer_template.tsx +++ b/x-pack/plugins/maps/public/classes/layers/choropleth_layer_wizard/layer_template.tsx @@ -24,7 +24,7 @@ import { EMSFileSelect } from '../../../components/ems_file_select'; import { GeoIndexPatternSelect } from '../../../components/geo_index_pattern_select'; import { SingleFieldSelect } from '../../../components/single_field_select'; import { getGeoFields, getSourceFields, getTermsFields } from '../../../index_pattern_util'; -import { getEmsFileLayers } from '../../../meta'; +import { getEmsFileLayers } from '../../../util'; import { getIndexPatternSelectComponent, getIndexPatternService } from '../../../kibana_services'; import { createEmsChoroplethLayerDescriptor, diff --git a/x-pack/plugins/maps/public/classes/layers/create_basemap_layer_descriptor.test.ts b/x-pack/plugins/maps/public/classes/layers/create_basemap_layer_descriptor.test.ts index 151db80da2142..1d21401778ae6 100644 --- a/x-pack/plugins/maps/public/classes/layers/create_basemap_layer_descriptor.test.ts +++ b/x-pack/plugins/maps/public/classes/layers/create_basemap_layer_descriptor.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -jest.mock('../../meta', () => { +jest.mock('../../util', () => { return {}; }); jest.mock('../../kibana_services', () => { @@ -33,7 +33,7 @@ import { createBasemapLayerDescriptor } from './create_basemap_layer_descriptor' describe('kibana.yml configured with map.tilemap.url', () => { beforeAll(() => { // eslint-disable-next-line @typescript-eslint/no-var-requires - require('../../meta').getKibanaTileMap = () => { + require('../../util').getKibanaTileMap = () => { return { url: 'myTileUrl', }; @@ -61,7 +61,7 @@ describe('kibana.yml configured with map.tilemap.url', () => { describe('EMS is enabled', () => { beforeAll(() => { // eslint-disable-next-line @typescript-eslint/no-var-requires - require('../../meta').getKibanaTileMap = () => { + require('../../util').getKibanaTileMap = () => { return null; }; // eslint-disable-next-line @typescript-eslint/no-var-requires @@ -95,7 +95,7 @@ describe('EMS is enabled', () => { describe('EMS is not enabled', () => { beforeAll(() => { // eslint-disable-next-line @typescript-eslint/no-var-requires - require('../../meta').getKibanaTileMap = () => { + require('../../util').getKibanaTileMap = () => { return null; }; // eslint-disable-next-line @typescript-eslint/no-var-requires diff --git a/x-pack/plugins/maps/public/classes/layers/create_basemap_layer_descriptor.ts b/x-pack/plugins/maps/public/classes/layers/create_basemap_layer_descriptor.ts index edffa5dc7e0cb..033b0de025ee1 100644 --- a/x-pack/plugins/maps/public/classes/layers/create_basemap_layer_descriptor.ts +++ b/x-pack/plugins/maps/public/classes/layers/create_basemap_layer_descriptor.ts @@ -7,7 +7,7 @@ import _ from 'lodash'; import { LayerDescriptor } from '../../../common/descriptor_types'; -import { getKibanaTileMap } from '../../meta'; +import { getKibanaTileMap } from '../../util'; import { getEMSSettings } from '../../kibana_services'; // @ts-expect-error import { KibanaTilemapSource } from '../sources/kibana_tilemap_source'; diff --git a/x-pack/plugins/maps/public/classes/layers/icons/top_hits_layer_icon.tsx b/x-pack/plugins/maps/public/classes/layers/icons/top_hits_layer_icon.tsx new file mode 100644 index 0000000000000..9e67c57895172 --- /dev/null +++ b/x-pack/plugins/maps/public/classes/layers/icons/top_hits_layer_icon.tsx @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FunctionComponent } from 'react'; + +export const TopHitsLayerIcon: FunctionComponent = () => ( + + + + + + + +); diff --git a/x-pack/plugins/maps/public/classes/layers/icons/tracks_layer_icon.tsx b/x-pack/plugins/maps/public/classes/layers/icons/tracks_layer_icon.tsx index 77b3efe5d2d83..c2d275ba3133d 100644 --- a/x-pack/plugins/maps/public/classes/layers/icons/tracks_layer_icon.tsx +++ b/x-pack/plugins/maps/public/classes/layers/icons/tracks_layer_icon.tsx @@ -13,9 +13,9 @@ export const TracksLayerIcon: FunctionComponent = () => ( className="mapLayersWizardIcon__highlight" d="M12.733 12.136h32.283v.545H12.935L4.452 19.98l-.356-.413 8.637-7.43z" /> - + - + diff --git a/x-pack/plugins/maps/public/classes/layers/load_layer_wizards.ts b/x-pack/plugins/maps/public/classes/layers/load_layer_wizards.ts index bed7599f89073..804352f5bede7 100644 --- a/x-pack/plugins/maps/public/classes/layers/load_layer_wizards.ts +++ b/x-pack/plugins/maps/public/classes/layers/load_layer_wizards.ts @@ -7,8 +7,10 @@ import { registerLayerWizard } from './layer_wizard_registry'; import { uploadLayerWizardConfig } from './file_upload_wizard'; -// @ts-ignore -import { esDocumentsLayerWizardConfig } from '../sources/es_search_source'; +import { + esDocumentsLayerWizardConfig, + esTopHitsLayerWizardConfig, +} from '../sources/es_search_source'; import { clustersLayerWizardConfig, heatmapLayerWizardConfig } from '../sources/es_geo_grid_source'; import { geoLineLayerWizardConfig } from '../sources/es_geo_line_source'; // @ts-ignore @@ -37,13 +39,13 @@ export function registerLayerWizards() { // Registration order determines display order registerLayerWizard(uploadLayerWizardConfig); - // @ts-ignore registerLayerWizard(esDocumentsLayerWizardConfig); // @ts-ignore registerLayerWizard(choroplethLayerWizardConfig); registerLayerWizard(clustersLayerWizardConfig); // @ts-ignore registerLayerWizard(heatmapLayerWizardConfig); + registerLayerWizard(esTopHitsLayerWizardConfig); registerLayerWizard(geoLineLayerWizardConfig); // @ts-ignore registerLayerWizard(point2PointLayerWizardConfig); diff --git a/x-pack/plugins/maps/public/classes/layers/solution_layers/observability/create_layer_descriptor.test.ts b/x-pack/plugins/maps/public/classes/layers/solution_layers/observability/create_layer_descriptor.test.ts index b9cfb0067abd2..5dd4f240c2ba9 100644 --- a/x-pack/plugins/maps/public/classes/layers/solution_layers/observability/create_layer_descriptor.test.ts +++ b/x-pack/plugins/maps/public/classes/layers/solution_layers/observability/create_layer_descriptor.test.ts @@ -5,6 +5,8 @@ * 2.0. */ +import { emsWorldLayerId } from '../../../../../common'; + jest.mock('../../../../kibana_services', () => { return { getIsDarkMode() { @@ -71,7 +73,7 @@ describe('createLayerDescriptor', () => { maxZoom: 24, minZoom: 0, sourceDescriptor: { - id: 'world_countries', + id: emsWorldLayerId, tooltipProperties: ['name', 'iso2'], type: 'EMS_FILE', }, diff --git a/x-pack/plugins/maps/public/classes/layers/solution_layers/observability/create_layer_descriptor.ts b/x-pack/plugins/maps/public/classes/layers/solution_layers/observability/create_layer_descriptor.ts index 03870e7668189..adf6f1d7f270d 100644 --- a/x-pack/plugins/maps/public/classes/layers/solution_layers/observability/create_layer_descriptor.ts +++ b/x-pack/plugins/maps/public/classes/layers/solution_layers/observability/create_layer_descriptor.ts @@ -18,6 +18,7 @@ import { import { AGG_TYPE, COLOR_MAP_TYPE, + emsWorldLayerId, FIELD_ORIGIN, GRID_RESOLUTION, RENDER_AS, @@ -182,7 +183,7 @@ export function createLayerDescriptor({ }, ], sourceDescriptor: EMSFileSource.createDescriptor({ - id: 'world_countries', + id: emsWorldLayerId, tooltipProperties: ['name', 'iso2'], }), style: VectorStyle.createDescriptor({ diff --git a/x-pack/plugins/maps/public/classes/layers/vector_tile_layer/vector_tile_layer.js b/x-pack/plugins/maps/public/classes/layers/vector_tile_layer/vector_tile_layer.js index 3e7d25266821c..5096e5e29bf23 100644 --- a/x-pack/plugins/maps/public/classes/layers/vector_tile_layer/vector_tile_layer.js +++ b/x-pack/plugins/maps/public/classes/layers/vector_tile_layer/vector_tile_layer.js @@ -8,7 +8,7 @@ import { TileLayer } from '../tile_layer/tile_layer'; import _ from 'lodash'; import { SOURCE_DATA_REQUEST_ID, LAYER_TYPE, LAYER_STYLE_TYPE } from '../../../../common/constants'; -import { isRetina } from '../../../meta'; +import { isRetina } from '../../../util'; import { addSpriteSheetToMapFromImageData, loadSpriteSheetImageData, diff --git a/x-pack/plugins/maps/public/classes/sources/ems_file_source/ems_file_source.tsx b/x-pack/plugins/maps/public/classes/sources/ems_file_source/ems_file_source.tsx index 5881982b893c1..c19ded6c2593e 100644 --- a/x-pack/plugins/maps/public/classes/sources/ems_file_source/ems_file_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/ems_file_source/ems_file_source.tsx @@ -18,7 +18,7 @@ import { VECTOR_SHAPE_TYPE, FORMAT_TYPE, } from '../../../../common/constants'; -import { getEmsFileLayers } from '../../../meta'; +import { fetchGeoJson, getEmsFileLayers } from '../../../util'; import { getDataSourceLabel } from '../../../../common/i18n_getters'; import { UpdateSourceEditor } from './update_source_editor'; import { EMSFileField } from '../../fields/ems_file_field'; @@ -123,12 +123,11 @@ export class EMSFileSource extends AbstractVectorSource implements IEmsFileSourc async getGeoJsonWithMeta(): Promise { const emsFileLayer = await this.getEMSFileLayer(); - // @ts-ignore - const featureCollection = await AbstractVectorSource.getGeoJson({ - format: emsFileLayer.getDefaultFormatType() as FORMAT_TYPE, - featureCollectionPath: 'data', - fetchUrl: emsFileLayer.getDefaultFormatUrl(), - }); + const featureCollection = await fetchGeoJson( + emsFileLayer.getDefaultFormatUrl(), + emsFileLayer.getDefaultFormatType() as FORMAT_TYPE, + 'data' + ); const emsIdField = emsFileLayer.getFields().find((field) => { return field.type === 'id'; diff --git a/x-pack/plugins/maps/public/classes/sources/ems_file_source/update_source_editor.tsx b/x-pack/plugins/maps/public/classes/sources/ems_file_source/update_source_editor.tsx index 3a8bb9c083afd..abac9cbe5d026 100644 --- a/x-pack/plugins/maps/public/classes/sources/ems_file_source/update_source_editor.tsx +++ b/x-pack/plugins/maps/public/classes/sources/ems_file_source/update_source_editor.tsx @@ -9,7 +9,7 @@ import React, { Component, Fragment } from 'react'; import { EuiTitle, EuiPanel, EuiSpacer } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { TooltipSelector } from '../../../components/tooltip_selector'; -import { getEmsFileLayers } from '../../../meta'; +import { getEmsFileLayers } from '../../../util'; import { IEmsFileSource } from './ems_file_source'; import { IField } from '../../fields/field'; import { OnSourceChangeArgs } from '../../../connected_components/layer_panel/view'; diff --git a/x-pack/plugins/maps/public/classes/sources/ems_tms_source/ems_tms_source.js b/x-pack/plugins/maps/public/classes/sources/ems_tms_source/ems_tms_source.js index f2216f2afd2da..97fb20b795bf6 100644 --- a/x-pack/plugins/maps/public/classes/sources/ems_tms_source/ems_tms_source.js +++ b/x-pack/plugins/maps/public/classes/sources/ems_tms_source/ems_tms_source.js @@ -7,7 +7,7 @@ import React from 'react'; import { AbstractTMSSource } from '../tms_source'; -import { getEmsTmsServices } from '../../../meta'; +import { getEmsTmsServices } from '../../../util'; import { UpdateSourceEditor } from './update_source_editor'; import { i18n } from '@kbn/i18n'; import { getDataSourceLabel } from '../../../../common/i18n_getters'; diff --git a/x-pack/plugins/maps/public/classes/sources/ems_tms_source/ems_tms_source.test.js b/x-pack/plugins/maps/public/classes/sources/ems_tms_source/ems_tms_source.test.js index 1b62aea180007..db5191e62fc04 100644 --- a/x-pack/plugins/maps/public/classes/sources/ems_tms_source/ems_tms_source.test.js +++ b/x-pack/plugins/maps/public/classes/sources/ems_tms_source/ems_tms_source.test.js @@ -5,7 +5,7 @@ * 2.0. */ -jest.mock('../../../meta', () => { +jest.mock('../../../util', () => { return { getEmsTmsServices: () => { class MockTMSService { diff --git a/x-pack/plugins/maps/public/classes/sources/ems_tms_source/tile_service_select.tsx b/x-pack/plugins/maps/public/classes/sources/ems_tms_source/tile_service_select.tsx index 5f0f406d53e86..e4a6fed934b8d 100644 --- a/x-pack/plugins/maps/public/classes/sources/ems_tms_source/tile_service_select.tsx +++ b/x-pack/plugins/maps/public/classes/sources/ems_tms_source/tile_service_select.tsx @@ -9,7 +9,7 @@ import React, { ChangeEvent, Component } from 'react'; import { EuiSelect, EuiSelectOption, EuiFormRow } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { getEmsTmsServices } from '../../../meta'; +import { getEmsTmsServices } from '../../../util'; import { getEmsUnavailableMessage } from '../../../components/ems_unavailable_message'; export const AUTO_SELECT = 'auto_select'; diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/__snapshots__/scaling_form.test.tsx.snap b/x-pack/plugins/maps/public/classes/sources/es_search_source/__snapshots__/scaling_form.test.tsx.snap index 120ff2e7adde3..03f2594f287ea 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/__snapshots__/scaling_form.test.tsx.snap +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/__snapshots__/scaling_form.test.tsx.snap @@ -31,12 +31,6 @@ exports[`scaling form should disable clusters option when clustering is not supp label="Limit results to 10000." onChange={[Function]} /> - - `; - -exports[`scaling form should render top hits form when scaling type is TOP_HITS 1`] = ` - - -
- -
-
- - -
- - - - - - - Use vector tiles for faster display of large datasets. - - } - delay="regular" - position="left" - > - - -
-
- - - - - - - -
-`; diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/__snapshots__/update_source_editor.test.js.snap b/x-pack/plugins/maps/public/classes/sources/es_search_source/__snapshots__/update_source_editor.test.js.snap index 3153f7b20f330..0ff94163f9230 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/__snapshots__/update_source_editor.test.js.snap +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/__snapshots__/update_source_editor.test.js.snap @@ -98,9 +98,6 @@ exports[`should enable sort order select when sort field provided 1`] = ` onChange={[Function]} scalingType="LIMIT" supportsClustering={false} - termFields={null} - topHitsSize={1} - topHitsSplitField="trackId" />
{ - const { - indexPattern, - geoFieldName, - filterByMapBounds, - scalingType, - topHitsSplitField, - topHitsSize, - } = this.state; + const { indexPattern, geoFieldName, filterByMapBounds, scalingType } = this.state; const sourceConfig = indexPattern && geoFieldName @@ -113,8 +103,6 @@ export class CreateSourceEditor extends Component { geoField: geoFieldName, filterByMapBounds, scalingType, - topHitsSplitField, - topHitsSize, } : null; this.props.onSourceConfigChange(sourceConfig); @@ -167,9 +155,6 @@ export class CreateSourceEditor extends Component { ) : null } - termFields={getTermsFields(this.state.indexPattern.fields)} - topHitsSplitField={this.state.topHitsSplitField} - topHitsSize={this.state.topHitsSize} /> ); diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_documents_layer_wizard.tsx b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_documents_layer_wizard.tsx index c0606b5f4aec6..26771c1bed023 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_documents_layer_wizard.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_documents_layer_wizard.tsx @@ -10,7 +10,6 @@ import React from 'react'; // @ts-ignore import { CreateSourceEditor } from './create_source_editor'; import { LayerWizard, RenderWizardArguments } from '../../layers/layer_wizard_registry'; -// @ts-ignore import { ESSearchSource, sourceTitle } from './es_search_source'; import { BlendedVectorLayer } from '../../layers/blended_vector_layer/blended_vector_layer'; import { VectorLayer } from '../../layers/vector_layer'; diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx index eae00710c4c25..168448b6f72a0 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx @@ -60,6 +60,7 @@ import { ITooltipProperty } from '../../tooltips/tooltip_property'; import { DataRequest } from '../../util/data_request'; import { SortDirection, SortDirectionNumeric } from '../../../../../../../src/plugins/data/common'; import { isValidStringConfig } from '../../util/valid_string_config'; +import { TopHitsUpdateSourceEditor } from './top_hits'; export const sourceTitle = i18n.translate('xpack.maps.source.esSearchTitle', { defaultMessage: 'Documents', @@ -166,6 +167,22 @@ export class ESSearchSource extends AbstractESSource implements ITiledSingleLaye } renderSourceSettingsEditor(sourceEditorArgs: SourceEditorArgs): ReactElement | null { + if (this._isTopHits()) { + return ( + + ); + } + const getGeoField = () => { return this._getGeoField(); }; @@ -180,8 +197,6 @@ export class ESSearchSource extends AbstractESSource implements ITiledSingleLaye sortOrder={this._descriptor.sortOrder} scalingType={this._descriptor.scalingType} filterByMapBounds={this.isFilterByMapBounds()} - topHitsSplitField={this._descriptor.topHitsSplitField} - topHitsSize={this._descriptor.topHitsSize} /> ); } @@ -658,6 +673,7 @@ export class ESSearchSource extends AbstractESSource implements ITiledSingleLaye getSyncMeta(): VectorSourceSyncMeta | null { return { + filterByMapBounds: this._descriptor.filterByMapBounds, sortField: this._descriptor.sortField, sortOrder: this._descriptor.sortOrder, scalingType: this._descriptor.scalingType, diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/index.ts b/x-pack/plugins/maps/public/classes/sources/es_search_source/index.ts index 73e7963024471..75217c0a29c08 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/index.ts +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/index.ts @@ -11,3 +11,4 @@ export { createDefaultLayerDescriptor, esDocumentsLayerWizardConfig, } from './es_documents_layer_wizard'; +export { esTopHitsLayerWizardConfig } from './top_hits'; diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/scaling_form.test.tsx b/x-pack/plugins/maps/public/classes/sources/es_search_source/scaling_form.test.tsx index fe47208c32690..b02eacc133467 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/scaling_form.test.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/scaling_form.test.tsx @@ -26,8 +26,6 @@ const defaultProps = { scalingType: SCALING_TYPES.LIMIT, supportsClustering: true, termFields: [], - topHitsSplitField: null, - topHitsSize: 1, }; describe('scaling form', () => { @@ -48,12 +46,4 @@ describe('scaling form', () => { expect(component).toMatchSnapshot(); }); - - test('should render top hits form when scaling type is TOP_HITS', async () => { - const component = shallow( - - ); - - expect(component).toMatchSnapshot(); - }); }); diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/scaling_form.tsx b/x-pack/plugins/maps/public/classes/sources/es_search_source/scaling_form.tsx index 6190c7ed8df3f..b9ce43dbbdad4 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/scaling_form.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/scaling_form.tsx @@ -19,19 +19,9 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { SingleFieldSelect } from '../../../components/single_field_select'; import { getIndexPatternService } from '../../../kibana_services'; -// @ts-ignore -import { ValidatedRange } from '../../../components/validated_range'; -import { - DEFAULT_MAX_INNER_RESULT_WINDOW, - DEFAULT_MAX_RESULT_WINDOW, - LAYER_TYPE, - SCALING_TYPES, -} from '../../../../common/constants'; -// @ts-ignore +import { DEFAULT_MAX_RESULT_WINDOW, LAYER_TYPE, SCALING_TYPES } from '../../../../common/constants'; import { loadIndexSettings } from './load_index_settings'; -import { IFieldType } from '../../../../../../../src/plugins/data/public'; import { OnSourceChangeArgs } from '../../../connected_components/layer_panel/view'; interface Props { @@ -41,19 +31,14 @@ interface Props { scalingType: SCALING_TYPES; supportsClustering: boolean; clusteringDisabledReason?: string | null; - termFields: IFieldType[]; - topHitsSplitField: string | null; - topHitsSize: number; } interface State { - maxInnerResultWindow: number; maxResultWindow: number; } export class ScalingForm extends Component { state = { - maxInnerResultWindow: DEFAULT_MAX_INNER_RESULT_WINDOW, maxResultWindow: DEFAULT_MAX_RESULT_WINDOW, }; _isMounted = false; @@ -70,11 +55,9 @@ export class ScalingForm extends Component { async loadIndexSettings() { try { const indexPattern = await getIndexPatternService().get(this.props.indexPatternId); - const { maxInnerResultWindow, maxResultWindow } = await loadIndexSettings( - indexPattern!.title - ); + const { maxResultWindow } = await loadIndexSettings(indexPattern!.title); if (this._isMounted) { - this.setState({ maxInnerResultWindow, maxResultWindow }); + this.setState({ maxResultWindow }); } } catch (err) { return; @@ -98,71 +81,6 @@ export class ScalingForm extends Component { this.props.onChange({ propName: 'filterByMapBounds', value: event.target.checked }); }; - _onTopHitsSplitFieldChange = (topHitsSplitField?: string) => { - if (!topHitsSplitField) { - return; - } - this.props.onChange({ propName: 'topHitsSplitField', value: topHitsSplitField }); - }; - - _onTopHitsSizeChange = (size: number) => { - this.props.onChange({ propName: 'topHitsSize', value: size }); - }; - - _renderTopHitsForm() { - let sizeSlider; - if (this.props.topHitsSplitField) { - sizeSlider = ( - - - - ); - } - - return ( - - - - - - {sizeSlider} - - ); - } - _renderClusteringRadio() { const clusteringRadio = ( { render() { let filterByBoundsSwitch; - if ( - this.props.scalingType === SCALING_TYPES.TOP_HITS || - this.props.scalingType === SCALING_TYPES.LIMIT - ) { + if (this.props.scalingType === SCALING_TYPES.LIMIT) { filterByBoundsSwitch = ( { ); } - let topHitsOptionsForm = null; - if (this.props.scalingType === SCALING_TYPES.TOP_HITS) { - topHitsOptionsForm = ( - - - {this._renderTopHitsForm()} - - ); - } - return ( @@ -267,21 +172,12 @@ export class ScalingForm extends Component { checked={this.props.scalingType === SCALING_TYPES.LIMIT} onChange={() => this._onScalingTypeChange(SCALING_TYPES.LIMIT)} /> - this._onScalingTypeChange(SCALING_TYPES.TOP_HITS)} - /> {this._renderClusteringRadio()} {this._renderMVTRadio()} {filterByBoundsSwitch} - {topHitsOptionsForm} ); } diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/top_hits/create_source_editor.tsx b/x-pack/plugins/maps/public/classes/sources/es_search_source/top_hits/create_source_editor.tsx new file mode 100644 index 0000000000000..ec656be3efeae --- /dev/null +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/top_hits/create_source_editor.tsx @@ -0,0 +1,162 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { Component } from 'react'; +import { EuiPanel } from '@elastic/eui'; + +import { SCALING_TYPES } from '../../../../../common/constants'; +import { GeoFieldSelect } from '../../../../components/geo_field_select'; +import { GeoIndexPatternSelect } from '../../../../components/geo_index_pattern_select'; +import { getGeoFields, getTermsFields, getSortFields } from '../../../../index_pattern_util'; +import { ESSearchSourceDescriptor } from '../../../../../common/descriptor_types'; +import { + IndexPattern, + IFieldType, + SortDirection, +} from '../../../../../../../../src/plugins/data/common'; +import { TopHitsForm } from './top_hits_form'; +import { OnSourceChangeArgs } from '../../../../connected_components/layer_panel/view'; + +interface Props { + onSourceConfigChange: (sourceConfig: Partial | null) => void; +} + +interface State { + indexPattern: IndexPattern | null; + geoFields: IFieldType[]; + geoFieldName: string | null; + sortField: string | null; + sortFields: IFieldType[]; + sortOrder: SortDirection; + termFields: IFieldType[]; + topHitsSplitField: string | null; + topHitsSize: number; +} + +export class CreateSourceEditor extends Component { + state: State = { + indexPattern: null, + geoFields: [], + geoFieldName: null, + sortField: null, + sortFields: [], + sortOrder: SortDirection.desc, + termFields: [], + topHitsSplitField: null, + topHitsSize: 1, + }; + + _onIndexPatternSelect = (indexPattern: IndexPattern) => { + const geoFields = getGeoFields(indexPattern.fields); + + this.setState( + { + indexPattern, + geoFields, + geoFieldName: geoFields.length ? geoFields[0].name : null, + sortField: indexPattern.timeFieldName ? indexPattern.timeFieldName : null, + sortFields: getSortFields(indexPattern.fields), + termFields: getTermsFields(indexPattern.fields), + topHitsSplitField: null, + }, + this._previewLayer + ); + }; + + _onGeoFieldSelect = (geoFieldName?: string) => { + this.setState({ geoFieldName: geoFieldName ? geoFieldName : null }, this._previewLayer); + }; + + _onTopHitsPropChange = ({ propName, value }: OnSourceChangeArgs) => { + this.setState( + // @ts-expect-error + { [propName]: value }, + this._previewLayer + ); + }; + + _previewLayer = () => { + const { + indexPattern, + geoFieldName, + sortField, + sortOrder, + topHitsSplitField, + topHitsSize, + } = this.state; + + const tooltipProperties: string[] = []; + if (topHitsSplitField) { + tooltipProperties.push(topHitsSplitField); + } + if (indexPattern && indexPattern.timeFieldName) { + tooltipProperties.push(indexPattern.timeFieldName); + } + + const sourceConfig = + indexPattern && geoFieldName && sortField && topHitsSplitField + ? { + indexPatternId: indexPattern.id, + geoField: geoFieldName, + scalingType: SCALING_TYPES.TOP_HITS, + sortField, + sortOrder, + tooltipProperties, + topHitsSplitField, + topHitsSize, + } + : null; + this.props.onSourceConfigChange(sourceConfig); + }; + + _renderGeoSelect() { + return this.state.indexPattern ? ( + + ) : null; + } + + _renderTopHitsPanel() { + if (!this.state.indexPattern || !this.state.indexPattern.id || !this.state.geoFieldName) { + return null; + } + + return ( + + ); + } + + render() { + return ( + + + + {this._renderGeoSelect()} + + {this._renderTopHitsPanel()} + + ); + } +} diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/top_hits/index.ts b/x-pack/plugins/maps/public/classes/sources/es_search_source/top_hits/index.ts new file mode 100644 index 0000000000000..135ed7c991b3a --- /dev/null +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/top_hits/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { TopHitsUpdateSourceEditor } from './update_source_editor'; +export { esTopHitsLayerWizardConfig } from './wizard'; diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/top_hits/top_hits_form.tsx b/x-pack/plugins/maps/public/classes/sources/es_search_source/top_hits/top_hits_form.tsx new file mode 100644 index 0000000000000..e4f196e5e8a85 --- /dev/null +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/top_hits/top_hits_form.tsx @@ -0,0 +1,190 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { ChangeEvent, Component, Fragment } from 'react'; +import { EuiFormRow, EuiSelect } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { SingleFieldSelect } from '../../../../components/single_field_select'; +import { getIndexPatternService } from '../../../../kibana_services'; +// @ts-expect-error +import { ValidatedRange } from '../../../../components/validated_range'; +import { DEFAULT_MAX_INNER_RESULT_WINDOW } from '../../../../../common/constants'; +import { loadIndexSettings } from '../load_index_settings'; +import { OnSourceChangeArgs } from '../../../../connected_components/layer_panel/view'; +import { IFieldType, SortDirection } from '../../../../../../../../src/plugins/data/public'; + +interface Props { + indexPatternId: string; + isColumnCompressed?: boolean; + onChange: (args: OnSourceChangeArgs) => void; + sortField: string; + sortFields: IFieldType[]; + sortOrder: SortDirection; + termFields: IFieldType[]; + topHitsSplitField: string | null; + topHitsSize: number; +} + +interface State { + maxInnerResultWindow: number; +} + +export class TopHitsForm extends Component { + state = { + maxInnerResultWindow: DEFAULT_MAX_INNER_RESULT_WINDOW, + }; + _isMounted = false; + + componentDidMount() { + this._isMounted = true; + this.loadIndexSettings(); + } + + componentWillUnmount() { + this._isMounted = false; + } + + _onTopHitsSplitFieldChange = (topHitsSplitField?: string) => { + if (!topHitsSplitField) { + return; + } + this.props.onChange({ propName: 'topHitsSplitField', value: topHitsSplitField }); + }; + + _onTopHitsSizeChange = (size: number) => { + this.props.onChange({ propName: 'topHitsSize', value: size }); + }; + + _onSortFieldChange = (sortField?: string) => { + this.props.onChange({ propName: 'sortField', value: sortField }); + }; + + _onSortOrderChange = (event: ChangeEvent) => { + this.props.onChange({ propName: 'sortOrder', value: event.target.value }); + }; + + async loadIndexSettings() { + try { + const indexPattern = await getIndexPatternService().get(this.props.indexPatternId); + const { maxInnerResultWindow } = await loadIndexSettings(indexPattern!.title); + if (this._isMounted) { + this.setState({ maxInnerResultWindow }); + } + } catch (err) { + return; + } + } + + render() { + let sizeSlider; + let sortField; + let sortOrder; + if (this.props.topHitsSplitField) { + sizeSlider = ( + + + + ); + + sortField = ( + + + + ); + + sortOrder = ( + + + + ); + } + + return ( + + + + + + {sizeSlider} + + {sortField} + + {sortOrder} + + ); + } +} diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/top_hits/update_source_editor.tsx b/x-pack/plugins/maps/public/classes/sources/es_search_source/top_hits/update_source_editor.tsx new file mode 100644 index 0000000000000..90553d47e644a --- /dev/null +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/top_hits/update_source_editor.tsx @@ -0,0 +1,168 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { Component, Fragment } from 'react'; +import { EuiFormRow, EuiTitle, EuiPanel, EuiSpacer, EuiSwitch, EuiSwitchEvent } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { FIELD_ORIGIN } from '../../../../../common/constants'; +import { TooltipSelector } from '../../../../components/tooltip_selector'; + +import { getIndexPatternService } from '../../../../kibana_services'; +import { getTermsFields, getSortFields, getSourceFields } from '../../../../index_pattern_util'; +import { SortDirection, IFieldType } from '../../../../../../../../src/plugins/data/public'; +import { ESDocField } from '../../../fields/es_doc_field'; +import { OnSourceChangeArgs } from '../../../../connected_components/layer_panel/view'; +import { TopHitsForm } from './top_hits_form'; +import { ESSearchSource } from '../es_search_source'; +import { IField } from '../../../fields/field'; + +interface Props { + filterByMapBounds: boolean; + indexPatternId: string; + onChange: (args: OnSourceChangeArgs) => void; + tooltipFields: IField[]; + topHitsSplitField: string; + topHitsSize: number; + sortField: string; + sortOrder: SortDirection; + source: ESSearchSource; +} + +interface State { + loadError?: string; + sourceFields: IField[]; + termFields: IFieldType[]; + sortFields: IFieldType[]; +} + +export class TopHitsUpdateSourceEditor extends Component { + private _isMounted = false; + + state: State = { + sourceFields: [], + termFields: [], + sortFields: [], + }; + + componentDidMount() { + this._isMounted = true; + this.loadFields(); + } + + componentWillUnmount() { + this._isMounted = false; + } + + async loadFields() { + let indexPattern; + try { + indexPattern = await getIndexPatternService().get(this.props.indexPatternId); + } catch (err) { + if (this._isMounted) { + this.setState({ + loadError: i18n.translate('xpack.maps.source.esSearch.loadErrorMessage', { + defaultMessage: `Unable to find Index pattern {id}`, + values: { + id: this.props.indexPatternId, + }, + }), + }); + } + return; + } + + if (!this._isMounted) { + return; + } + + const rawTooltipFields = getSourceFields(indexPattern.fields); + const sourceFields = rawTooltipFields.map((field) => { + return new ESDocField({ + fieldName: field.name, + source: this.props.source, + origin: FIELD_ORIGIN.SOURCE, + }); + }); + + this.setState({ + sourceFields, + termFields: getTermsFields(indexPattern.fields), + sortFields: getSortFields(indexPattern.fields), + }); + } + _onTooltipPropertiesChange = (propertyNames: string[]) => { + this.props.onChange({ propName: 'tooltipProperties', value: propertyNames }); + }; + + _onFilterByMapBoundsChange = (event: EuiSwitchEvent) => { + this.props.onChange({ propName: 'filterByMapBounds', value: event.target.checked }); + }; + + render() { + return ( + + + +
+ +
+
+ + + + +
+ + + + +
+ +
+
+ + + + + + + + +
+ +
+ ); + } +} diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/top_hits/wizard.tsx b/x-pack/plugins/maps/public/classes/sources/es_search_source/top_hits/wizard.tsx new file mode 100644 index 0000000000000..e02ada305ecff --- /dev/null +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/top_hits/wizard.tsx @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import React from 'react'; +import { CreateSourceEditor } from './create_source_editor'; +import { LayerWizard, RenderWizardArguments } from '../../../layers/layer_wizard_registry'; +import { VectorLayer } from '../../../layers/vector_layer'; +import { LAYER_WIZARD_CATEGORY } from '../../../../../common/constants'; +import { TopHitsLayerIcon } from '../../../layers/icons/top_hits_layer_icon'; +import { ESSearchSourceDescriptor } from '../../../../../common/descriptor_types'; +import { ESSearchSource } from '../es_search_source'; + +export const esTopHitsLayerWizardConfig: LayerWizard = { + categories: [LAYER_WIZARD_CATEGORY.ELASTICSEARCH], + description: i18n.translate('xpack.maps.source.topHitsDescription', { + defaultMessage: + 'Display the most relevant documents per entity, e.g. the most recent GPS hits per vehicle.', + }), + icon: TopHitsLayerIcon, + renderWizard: ({ previewLayers, mapColors }: RenderWizardArguments) => { + const onSourceConfigChange = (sourceConfig: Partial | null) => { + if (!sourceConfig) { + previewLayers([]); + return; + } + + const sourceDescriptor = ESSearchSource.createDescriptor(sourceConfig); + const layerDescriptor = VectorLayer.createDescriptor({ sourceDescriptor }, mapColors); + previewLayers([layerDescriptor]); + }; + return ; + }, + title: i18n.translate('xpack.maps.source.topHitsTitle', { + defaultMessage: 'Top hits per entity', + }), +}; diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/update_source_editor.js b/x-pack/plugins/maps/public/classes/sources/es_search_source/update_source_editor.js index 1e870f423171f..8632666011065 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/update_source_editor.js +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/update_source_editor.js @@ -8,6 +8,7 @@ import React, { Fragment, Component } from 'react'; import PropTypes from 'prop-types'; import { EuiFormRow, EuiSelect, EuiTitle, EuiPanel, EuiSpacer } from '@elastic/eui'; +import { FIELD_ORIGIN } from '../../../../common/constants'; import { SingleFieldSelect } from '../../../components/single_field_select'; import { TooltipSelector } from '../../../components/tooltip_selector'; @@ -15,7 +16,6 @@ import { getIndexPatternService } from '../../../kibana_services'; import { i18n } from '@kbn/i18n'; import { getGeoTileAggNotSupportedReason, - getTermsFields, getSourceFields, supportsGeoTileAgg, } from '../../../index_pattern_util'; @@ -33,14 +33,11 @@ export class UpdateSourceEditor extends Component { sortField: PropTypes.string, sortOrder: PropTypes.string.isRequired, scalingType: PropTypes.string.isRequired, - topHitsSplitField: PropTypes.string, - topHitsSize: PropTypes.number.isRequired, source: PropTypes.object, }; state = { sourceFields: null, - termFields: null, sortFields: null, supportsClustering: false, mvtDisabledReason: null, @@ -94,6 +91,7 @@ export class UpdateSourceEditor extends Component { return new ESDocField({ fieldName: field.name, source: this.props.source, + origin: FIELD_ORIGIN.SOURCE, }); }); @@ -102,7 +100,6 @@ export class UpdateSourceEditor extends Component { clusteringDisabledReason: getGeoTileAggNotSupportedReason(geoField), mvtDisabledReason: null, sourceFields: sourceFields, - termFields: getTermsFields(indexPattern.fields), //todo change term fields to use fields sortFields: indexPattern.fields.filter( (field) => field.sortable && !indexPatterns.isNestedField(field) ), //todo change sort fields to use fields @@ -212,9 +209,6 @@ export class UpdateSourceEditor extends Component { scalingType={this.props.scalingType} supportsClustering={this.state.supportsClustering} clusteringDisabledReason={this.state.clusteringDisabledReason} - termFields={this.state.termFields} - topHitsSplitField={this.props.topHitsSplitField} - topHitsSize={this.props.topHitsSize} />
); diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/update_source_editor.test.js b/x-pack/plugins/maps/public/classes/sources/es_search_source/update_source_editor.test.js index 00a7b2b0b3490..f54947bc91d19 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/update_source_editor.test.js +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/update_source_editor.test.js @@ -26,8 +26,6 @@ const defaultProps = { tooltipFields: [], sortOrder: 'DESC', scalingType: SCALING_TYPES.LIMIT, - topHitsSplitField: 'trackId', - topHitsSize: 1, }; test('should render update source editor', async () => { diff --git a/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/create_source_editor.js b/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/create_source_editor.js index 436d05fdbc6e8..1278d84f103da 100644 --- a/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/create_source_editor.js +++ b/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/create_source_editor.js @@ -8,7 +8,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { EuiSelect, EuiFormRow, EuiPanel } from '@elastic/eui'; -import { getKibanaRegionList } from '../../../meta'; +import { getKibanaRegionList } from '../../../util'; import { i18n } from '@kbn/i18n'; export function CreateSourceEditor({ onSourceConfigChange }) { diff --git a/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/kibana_regionmap_layer_wizard.tsx b/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/kibana_regionmap_layer_wizard.tsx index 907b80e6405a6..9091e03fdf7f5 100644 --- a/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/kibana_regionmap_layer_wizard.tsx +++ b/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/kibana_regionmap_layer_wizard.tsx @@ -13,7 +13,7 @@ import { KibanaRegionmapSource, sourceTitle } from './kibana_regionmap_source'; import { VectorLayer } from '../../layers/vector_layer'; // @ts-ignore import { CreateSourceEditor } from './create_source_editor'; -import { getKibanaRegionList } from '../../../meta'; +import { getKibanaRegionList } from '../../../util'; import { LAYER_WIZARD_CATEGORY } from '../../../../common/constants'; export const kibanaRegionMapLayerWizardConfig: LayerWizard = { diff --git a/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/kibana_regionmap_source.ts b/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/kibana_regionmap_source.ts index 0f778f194ce3f..12e4b00c3c7b9 100644 --- a/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/kibana_regionmap_source.ts +++ b/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/kibana_regionmap_source.ts @@ -7,7 +7,7 @@ import { i18n } from '@kbn/i18n'; import { AbstractVectorSource, GeoJsonWithMeta } from '../vector_source'; -import { getKibanaRegionList } from '../../../meta'; +import { fetchGeoJson, getKibanaRegionList } from '../../../util'; import { getDataSourceLabel } from '../../../../common/i18n_getters'; import { FIELD_ORIGIN, FORMAT_TYPE, SOURCE_TYPES } from '../../../../common/constants'; import { KibanaRegionField } from '../../fields/kibana_region_field'; @@ -79,11 +79,12 @@ export class KibanaRegionmapSource extends AbstractVectorSource { async getGeoJsonWithMeta(): Promise { const vectorFileMeta = await this.getVectorFileMeta(); - const featureCollection = await AbstractVectorSource.getGeoJson({ - format: vectorFileMeta.format.type as FORMAT_TYPE, - featureCollectionPath: vectorFileMeta.meta.feature_collection_path, - fetchUrl: vectorFileMeta.url, - }); + const featureCollection = await fetchGeoJson( + vectorFileMeta.url, + vectorFileMeta.format.type as FORMAT_TYPE, + vectorFileMeta.meta.feature_collection_path + ); + return { data: featureCollection, meta: {}, diff --git a/x-pack/plugins/maps/public/classes/sources/kibana_tilemap_source/create_source_editor.js b/x-pack/plugins/maps/public/classes/sources/kibana_tilemap_source/create_source_editor.js index 8ec57d2b6f4fb..4d6939a3b7d45 100644 --- a/x-pack/plugins/maps/public/classes/sources/kibana_tilemap_source/create_source_editor.js +++ b/x-pack/plugins/maps/public/classes/sources/kibana_tilemap_source/create_source_editor.js @@ -9,7 +9,7 @@ import React, { useEffect } from 'react'; import PropTypes from 'prop-types'; import { EuiFieldText, EuiFormRow, EuiPanel } from '@elastic/eui'; -import { getKibanaTileMap } from '../../../meta'; +import { getKibanaTileMap } from '../../../util'; import { i18n } from '@kbn/i18n'; export function CreateSourceEditor({ onSourceConfigChange }) { diff --git a/x-pack/plugins/maps/public/classes/sources/kibana_tilemap_source/kibana_base_map_layer_wizard.tsx b/x-pack/plugins/maps/public/classes/sources/kibana_tilemap_source/kibana_base_map_layer_wizard.tsx index 8d18cda4e70dd..26893086ba8f7 100644 --- a/x-pack/plugins/maps/public/classes/sources/kibana_tilemap_source/kibana_base_map_layer_wizard.tsx +++ b/x-pack/plugins/maps/public/classes/sources/kibana_tilemap_source/kibana_base_map_layer_wizard.tsx @@ -13,7 +13,7 @@ import { CreateSourceEditor } from './create_source_editor'; // @ts-ignore import { KibanaTilemapSource, sourceTitle } from './kibana_tilemap_source'; import { TileLayer } from '../../layers/tile_layer/tile_layer'; -import { getKibanaTileMap } from '../../../meta'; +import { getKibanaTileMap } from '../../../util'; import { LAYER_WIZARD_CATEGORY } from '../../../../common/constants'; export const kibanaBasemapLayerWizardConfig: LayerWizard = { diff --git a/x-pack/plugins/maps/public/classes/sources/kibana_tilemap_source/kibana_tilemap_source.js b/x-pack/plugins/maps/public/classes/sources/kibana_tilemap_source/kibana_tilemap_source.js index 0b88fe2e13905..94d082d8744e8 100644 --- a/x-pack/plugins/maps/public/classes/sources/kibana_tilemap_source/kibana_tilemap_source.js +++ b/x-pack/plugins/maps/public/classes/sources/kibana_tilemap_source/kibana_tilemap_source.js @@ -6,7 +6,7 @@ */ import { AbstractTMSSource } from '../tms_source'; -import { getKibanaTileMap } from '../../../meta'; +import { getKibanaTileMap } from '../../../util'; import { i18n } from '@kbn/i18n'; import { getDataSourceLabel } from '../../../../common/i18n_getters'; import _ from 'lodash'; diff --git a/x-pack/plugins/maps/public/classes/sources/source.ts b/x-pack/plugins/maps/public/classes/sources/source.ts index 7c2aaf714c34e..25e3595d6dffa 100644 --- a/x-pack/plugins/maps/public/classes/sources/source.ts +++ b/x-pack/plugins/maps/public/classes/sources/source.ts @@ -84,7 +84,7 @@ export class AbstractSource implements ISource { } async supportsFitToBounds(): Promise { - return true; + return false; } /** diff --git a/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.tsx b/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.tsx index 5474e62e175d1..e86e459851c70 100644 --- a/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.tsx @@ -5,13 +5,9 @@ * 2.0. */ -// @ts-expect-error -import * as topojson from 'topojson-client'; -import _ from 'lodash'; -import { i18n } from '@kbn/i18n'; import { FeatureCollection, GeoJsonProperties } from 'geojson'; import { Filter, TimeRange } from 'src/plugins/data/public'; -import { FORMAT_TYPE, VECTOR_SHAPE_TYPE } from '../../../../common/constants'; +import { VECTOR_SHAPE_TYPE } from '../../../../common/constants'; import { TooltipProperty, ITooltipProperty } from '../../tooltips/tooltip_property'; import { AbstractSource, ISource } from '../source'; import { IField } from '../../fields/field'; @@ -85,48 +81,6 @@ export interface ITiledSingleLayerVectorSource extends IVectorSource { } export class AbstractVectorSource extends AbstractSource implements IVectorSource { - static async getGeoJson({ - format, - featureCollectionPath, - fetchUrl, - }: { - format: FORMAT_TYPE; - featureCollectionPath: string; - fetchUrl: string; - }) { - let fetchedJson; - try { - const response = await fetch(fetchUrl); - if (!response.ok) { - throw new Error('Request failed'); - } - fetchedJson = await response.json(); - } catch (e) { - throw new Error( - i18n.translate('xpack.maps.source.vetorSource.requestFailedErrorMessage', { - defaultMessage: `Unable to fetch vector shapes from url: {fetchUrl}`, - values: { fetchUrl }, - }) - ); - } - - if (format === FORMAT_TYPE.GEOJSON) { - return fetchedJson; - } - - if (format === FORMAT_TYPE.TOPOJSON) { - const features = _.get(fetchedJson, `objects.${featureCollectionPath}`); - return topojson.feature(fetchedJson, features); - } - - throw new Error( - i18n.translate('xpack.maps.source.vetorSource.formatErrorMessage', { - defaultMessage: `Unable to fetch vector shapes from url: {format}`, - values: { format }, - }) - ); - } - getFieldNames(): string[] { return []; } @@ -147,6 +101,10 @@ export class AbstractVectorSource extends AbstractSource implements IVectorSourc return false; } + async supportsFitToBounds(): Promise { + return true; + } + async getBoundsForFilters( boundsFilters: BoundsFilters, registerCancelCallback: (callback: () => void) => void diff --git a/x-pack/plugins/maps/public/components/ems_file_select.tsx b/x-pack/plugins/maps/public/components/ems_file_select.tsx index 64ae57fc81dcf..3d23854efb4fb 100644 --- a/x-pack/plugins/maps/public/components/ems_file_select.tsx +++ b/x-pack/plugins/maps/public/components/ems_file_select.tsx @@ -10,7 +10,7 @@ import { EuiComboBox, EuiComboBoxOptionOption, EuiFormRow, EuiSelect } from '@el import { i18n } from '@kbn/i18n'; import { FileLayer } from '@elastic/ems-client'; -import { getEmsFileLayers } from '../meta'; +import { getEmsFileLayers } from '../util'; import { getEmsUnavailableMessage } from './ems_unavailable_message'; interface Props { diff --git a/x-pack/plugins/maps/public/components/geo_field_select.tsx b/x-pack/plugins/maps/public/components/geo_field_select.tsx new file mode 100644 index 0000000000000..0b04ec7146611 --- /dev/null +++ b/x-pack/plugins/maps/public/components/geo_field_select.tsx @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiFormRow } from '@elastic/eui'; +import { SingleFieldSelect } from './single_field_select'; +import { IFieldType } from '../../../../../src/plugins/data/common'; + +interface Props { + value: string; + geoFields: IFieldType[]; + onChange: (geoFieldName?: string) => void; +} + +export function GeoFieldSelect(props: Props) { + return ( + + + + ); +} diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.tsx b/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.tsx index 5e4c3c9b1981f..66c9a2462736a 100644 --- a/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.tsx +++ b/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.tsx @@ -33,7 +33,7 @@ import { RawValue, ZOOM_PRECISION, } from '../../../common/constants'; -import { getGlyphUrl, isRetina } from '../../meta'; +import { getGlyphUrl, isRetina } from '../../util'; import { syncLayerOrder } from './sort_layers'; // @ts-expect-error import { removeOrphanedSourcesAndLayers, addSpritesheetToMap } from './utils'; diff --git a/x-pack/plugins/maps/public/ems_autosuggest/ems_autosuggest.test.ts b/x-pack/plugins/maps/public/ems_autosuggest/ems_autosuggest.test.ts new file mode 100644 index 0000000000000..34a53be48a5cd --- /dev/null +++ b/x-pack/plugins/maps/public/ems_autosuggest/ems_autosuggest.test.ts @@ -0,0 +1,171 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { suggestEMSTermJoinConfig } from './ems_autosuggest'; +import { FORMAT_TYPE } from '../../common'; +import { FeatureCollection } from 'geojson'; + +class MockFileLayer { + private readonly _url: string; + private readonly _id: string; + private readonly _fields: Array<{ id: string }>; + + constructor(url: string, fields: Array<{ id: string }>) { + this._url = url; + this._id = url; + this._fields = fields; + } + getDefaultFormatUrl() { + return this._url; + } + + getFields() { + return this._fields; + } + + getDefaultFormatType() { + return FORMAT_TYPE.GEOJSON; + } + + hasId(id: string) { + return id === this._id; + } +} + +jest.mock('../util', () => { + return { + async getEmsFileLayers() { + return [ + new MockFileLayer('world_countries', [{ id: 'iso2' }, { id: 'iso3' }]), + new MockFileLayer('zips', [{ id: 'zip' }]), + ]; + }, + async fetchGeoJson(url: string): Promise { + if (url === 'world_countries') { + return ({ + type: 'FeatureCollection', + features: [ + { properties: { iso2: 'CA', iso3: 'CAN' } }, + { properties: { iso2: 'US', iso3: 'USA' } }, + ], + } as unknown) as FeatureCollection; + } else if (url === 'zips') { + return ({ + type: 'FeatureCollection', + features: [{ properties: { zip: '40204' } }, { properties: { zip: '40205' } }], + } as unknown) as FeatureCollection; + } else { + throw new Error(`unrecognized mock url ${url}`); + } + }, + }; +}); + +describe('suggestEMSTermJoinConfig', () => { + test('no info provided', async () => { + const termJoinConfig = await suggestEMSTermJoinConfig({}); + expect(termJoinConfig).toBe(null); + }); + + describe('validate common column names', () => { + test('ecs region', async () => { + const termJoinConfig = await suggestEMSTermJoinConfig({ + sampleValuesColumnName: 'destination.geo.region_iso_code', + }); + expect(termJoinConfig).toEqual({ + layerId: 'administrative_regions_lvl2', + field: 'region_iso_code', + }); + }); + + test('ecs country', async () => { + const termJoinConfig = await suggestEMSTermJoinConfig({ + sampleValuesColumnName: 'country_iso_code', + }); + expect(termJoinConfig).toEqual({ + layerId: 'world_countries', + field: 'iso2', + }); + }); + + test('country', async () => { + const termJoinConfig = await suggestEMSTermJoinConfig({ + sampleValuesColumnName: 'Country_name', + }); + expect(termJoinConfig).toEqual({ + layerId: 'world_countries', + field: 'name', + }); + }); + + test('unknown name', async () => { + const termJoinConfig = await suggestEMSTermJoinConfig({ + sampleValuesColumnName: 'cntry', + }); + expect(termJoinConfig).toEqual(null); + }); + }); + + describe('validate well known formats', () => { + test('5-digit zip code', async () => { + const termJoinConfig = await suggestEMSTermJoinConfig({ + sampleValues: ['90201', 40204], + }); + expect(termJoinConfig).toEqual({ + layerId: 'usa_zip_codes', + field: 'zip', + }); + }); + + test('mismatch', async () => { + const termJoinConfig = await suggestEMSTermJoinConfig({ + sampleValues: ['90201', 'foobar'], + }); + expect(termJoinConfig).toEqual(null); + }); + }); + + describe('validate based on EMS data', () => { + test('Should validate with zip codes layer', async () => { + const termJoinConfig = await suggestEMSTermJoinConfig({ + sampleValues: ['40204', 40205], + emsLayerIds: ['world_countries', 'zips'], + }); + expect(termJoinConfig).toEqual({ + layerId: 'zips', + field: 'zip', + }); + }); + + test('Should not validate with faulty zip codes', async () => { + const termJoinConfig = await suggestEMSTermJoinConfig({ + sampleValues: ['40204', '00000'], + emsLayerIds: ['world_countries', 'zips'], + }); + expect(termJoinConfig).toEqual(null); + }); + + test('Should validate against countries', async () => { + const termJoinConfig = await suggestEMSTermJoinConfig({ + sampleValues: ['USA', 'USA', 'CAN'], + emsLayerIds: ['world_countries', 'zips'], + }); + expect(termJoinConfig).toEqual({ + layerId: 'world_countries', + field: 'iso3', + }); + }); + + test('Should not validate against missing countries', async () => { + const termJoinConfig = await suggestEMSTermJoinConfig({ + sampleValues: ['USA', 'BEL', 'CAN'], + emsLayerIds: ['world_countries', 'zips'], + }); + expect(termJoinConfig).toEqual(null); + }); + }); +}); diff --git a/x-pack/plugins/maps/public/ems_autosuggest/ems_autosuggest.ts b/x-pack/plugins/maps/public/ems_autosuggest/ems_autosuggest.ts new file mode 100644 index 0000000000000..1d5c1529a004e --- /dev/null +++ b/x-pack/plugins/maps/public/ems_autosuggest/ems_autosuggest.ts @@ -0,0 +1,201 @@ +/* + * 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 { FileLayer } from '@elastic/ems-client'; +import { getEmsFileLayers, fetchGeoJson } from '../util'; +import { FORMAT_TYPE, emsWorldLayerId, emsRegionLayerId, emsUsaZipLayerId } from '../../common'; + +export interface SampleValuesConfig { + emsLayerIds?: string[]; + sampleValues?: Array; + sampleValuesColumnName?: string; +} + +export interface EMSTermJoinConfig { + layerId: string; + field: string; +} + +const wellKnownColumnNames = [ + { + regex: /(geo\.){0,}country_iso_code$/i, // ECS postfix for country + emsConfig: { + layerId: emsWorldLayerId, + field: 'iso2', + }, + }, + { + regex: /(geo\.){0,}region_iso_code$/i, // ECS postfixn for region + emsConfig: { + layerId: emsRegionLayerId, + field: 'region_iso_code', + }, + }, + { + regex: /^country/i, // anything starting with country + emsConfig: { + layerId: emsWorldLayerId, + field: 'name', + }, + }, +]; + +const wellKnownColumnFormats = [ + { + regex: /(^\d{5}$)/i, // 5-digit zipcode + emsConfig: { + layerId: emsUsaZipLayerId, + field: 'zip', + }, + }, +]; + +interface UniqueMatch { + config: { layerId: string; field: string }; + count: number; +} + +export async function suggestEMSTermJoinConfig( + sampleValuesConfig: SampleValuesConfig +): Promise { + const matches: EMSTermJoinConfig[] = []; + + if (sampleValuesConfig.sampleValuesColumnName) { + matches.push(...suggestByName(sampleValuesConfig.sampleValuesColumnName)); + } + + if (sampleValuesConfig.sampleValues && sampleValuesConfig.sampleValues.length) { + if (sampleValuesConfig.emsLayerIds && sampleValuesConfig.emsLayerIds.length) { + matches.push( + ...(await suggestByEMSLayerIds( + sampleValuesConfig.emsLayerIds, + sampleValuesConfig.sampleValues + )) + ); + } else { + matches.push(...suggestByValues(sampleValuesConfig.sampleValues)); + } + } + + const uniqMatches: UniqueMatch[] = matches.reduce((accum: UniqueMatch[], match) => { + const found = accum.find((m) => { + return m.config.layerId === match.layerId && m.config.field === match.layerId; + }); + + if (found) { + found.count += 1; + } else { + accum.push({ + config: match, + count: 1, + }); + } + + return accum; + }, []); + + uniqMatches.sort((a, b) => { + return b.count - a.count; + }); + + return uniqMatches.length ? uniqMatches[0].config : null; +} + +function suggestByName(columnName: string): EMSTermJoinConfig[] { + const matches = wellKnownColumnNames.filter((wellknown) => { + return columnName.match(wellknown.regex); + }); + + return matches.map((m) => { + return m.emsConfig; + }); +} + +function suggestByValues(values: Array): EMSTermJoinConfig[] { + const matches = wellKnownColumnFormats.filter((wellknown) => { + for (let i = 0; i < values.length; i++) { + const value = values[i].toString(); + if (!value.match(wellknown.regex)) { + return false; + } + } + return true; + }); + + return matches.map((m) => { + return m.emsConfig; + }); +} + +function existsInEMS(emsJson: any, emsFieldId: string, sampleValue: string): boolean { + for (let i = 0; i < emsJson.features.length; i++) { + const emsFieldValue = emsJson.features[i].properties[emsFieldId].toString(); + if (emsFieldValue.toString() === sampleValue) { + return true; + } + } + return false; +} + +function matchesEmsField(emsJson: any, emsFieldId: string, sampleValues: Array) { + for (let j = 0; j < sampleValues.length; j++) { + const sampleValue = sampleValues[j].toString(); + if (!existsInEMS(emsJson, emsFieldId, sampleValue)) { + return false; + } + } + return true; +} + +async function getMatchesForEMSLayer( + emsLayerId: string, + sampleValues: Array +): Promise { + const fileLayers: FileLayer[] = await getEmsFileLayers(); + const emsFileLayer: FileLayer | undefined = fileLayers.find((fl: FileLayer) => + fl.hasId(emsLayerId) + ); + + if (!emsFileLayer) { + return []; + } + + const emsFields = emsFileLayer.getFields(); + const url = emsFileLayer.getDefaultFormatUrl(); + + try { + const emsJson = await fetchGeoJson( + url, + emsFileLayer.getDefaultFormatType() as FORMAT_TYPE, + 'data' + ); + const matches: EMSTermJoinConfig[] = []; + for (let f = 0; f < emsFields.length; f++) { + if (matchesEmsField(emsJson, emsFields[f].id, sampleValues)) { + matches.push({ + layerId: emsLayerId, + field: emsFields[f].id, + }); + } + } + return matches; + } catch (e) { + return []; + } +} + +async function suggestByEMSLayerIds( + emsLayerIds: string[], + values: Array +): Promise { + const matches = []; + for (const emsLayerId of emsLayerIds) { + const layerIdMathes = await getMatchesForEMSLayer(emsLayerId, values); + matches.push(...layerIdMathes); + } + return matches; +} diff --git a/x-pack/plugins/maps/public/ems_autosuggest/index.ts b/x-pack/plugins/maps/public/ems_autosuggest/index.ts new file mode 100644 index 0000000000000..86ed9e4fa70e1 --- /dev/null +++ b/x-pack/plugins/maps/public/ems_autosuggest/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './ems_autosuggest'; diff --git a/x-pack/plugins/maps/public/index_pattern_util.ts b/x-pack/plugins/maps/public/index_pattern_util.ts index f7894085b15ac..3b1cb461c8779 100644 --- a/x-pack/plugins/maps/public/index_pattern_util.ts +++ b/x-pack/plugins/maps/public/index_pattern_util.ts @@ -56,6 +56,12 @@ export function getTermsFields(fields: IFieldType[]): IFieldType[] { }); } +export function getSortFields(fields: IFieldType[]): IFieldType[] { + return fields.filter((field) => { + return field.sortable && !indexPatterns.isNestedField(field); + }); +} + export function getAggregatableGeoFieldTypes(): string[] { const aggregatableFieldTypes = [ES_GEO_FIELD_TYPE.GEO_POINT]; if (getIsGoldPlus()) { diff --git a/x-pack/plugins/maps/public/lazy_load_bundle/index.ts b/x-pack/plugins/maps/public/lazy_load_bundle/index.ts index 0bf604a26544b..3e5e2d54422d6 100644 --- a/x-pack/plugins/maps/public/lazy_load_bundle/index.ts +++ b/x-pack/plugins/maps/public/lazy_load_bundle/index.ts @@ -14,6 +14,7 @@ import { MapEmbeddableConfig, MapEmbeddableInput, MapEmbeddableOutput } from '.. import { SourceRegistryEntry } from '../classes/sources/source_registry'; import { LayerWizard } from '../classes/layers/layer_wizard_registry'; import type { CreateLayerDescriptorParams } from '../classes/sources/es_search_source'; +import type { EMSTermJoinConfig, SampleValuesConfig } from '../ems_autosuggest'; let loadModulesPromise: Promise; @@ -74,6 +75,7 @@ interface LazyLoadedMapModules { }) => LayerDescriptor | null; createBasemapLayerDescriptor: () => LayerDescriptor | null; createESSearchSourceLayerDescriptor: (params: CreateLayerDescriptorParams) => LayerDescriptor; + suggestEMSTermJoinConfig: (config: SampleValuesConfig) => Promise; } export async function lazyLoadMapModules(): Promise { @@ -94,6 +96,7 @@ export async function lazyLoadMapModules(): Promise { createRegionMapLayerDescriptor, createBasemapLayerDescriptor, createESSearchSourceLayerDescriptor, + suggestEMSTermJoinConfig, } = await import('./lazy'); resolve({ @@ -108,6 +111,7 @@ export async function lazyLoadMapModules(): Promise { createRegionMapLayerDescriptor, createBasemapLayerDescriptor, createESSearchSourceLayerDescriptor, + suggestEMSTermJoinConfig, }); }); return loadModulesPromise; diff --git a/x-pack/plugins/maps/public/lazy_load_bundle/lazy/index.ts b/x-pack/plugins/maps/public/lazy_load_bundle/lazy/index.ts index 85b58da0ab09a..e7f5df49527b7 100644 --- a/x-pack/plugins/maps/public/lazy_load_bundle/lazy/index.ts +++ b/x-pack/plugins/maps/public/lazy_load_bundle/lazy/index.ts @@ -16,3 +16,4 @@ export { createTileMapLayerDescriptor } from '../../classes/layers/create_tile_m export { createRegionMapLayerDescriptor } from '../../classes/layers/create_region_map_layer_descriptor'; export { createBasemapLayerDescriptor } from '../../classes/layers/create_basemap_layer_descriptor'; export { createLayerDescriptor as createESSearchSourceLayerDescriptor } from '../../classes/sources/es_search_source'; +export { suggestEMSTermJoinConfig } from '../../ems_autosuggest'; diff --git a/x-pack/plugins/maps/public/plugin.ts b/x-pack/plugins/maps/public/plugin.ts index 7ddab6bf509ff..ad8846bd48b60 100644 --- a/x-pack/plugins/maps/public/plugin.ts +++ b/x-pack/plugins/maps/public/plugin.ts @@ -47,8 +47,13 @@ import type { EmbeddableSetup, EmbeddableStart } from '../../../../src/plugins/e import { MapsXPackConfig, MapsConfigType } from '../config'; import { getAppTitle } from '../common/i18n_getters'; import { lazyLoadMapModules } from './lazy_load_bundle'; -import { MapsStartApi } from './api'; -import { createLayerDescriptors, registerLayerWizard, registerSource } from './api'; +import { + createLayerDescriptors, + registerLayerWizard, + registerSource, + MapsStartApi, + suggestEMSTermJoinConfig, +} from './api'; import type { SharePluginSetup, SharePluginStart } from '../../../../src/plugins/share/public'; import type { MapsEmsPluginSetup } from '../../../../src/plugins/maps_ems/public'; import type { DataPublicPluginStart } from '../../../../src/plugins/data/public'; @@ -177,6 +182,7 @@ export class MapsPlugin createLayerDescriptors, registerLayerWizard, registerSource, + suggestEMSTermJoinConfig, }; } } diff --git a/x-pack/plugins/maps/public/meta.test.js b/x-pack/plugins/maps/public/util.test.js similarity index 98% rename from x-pack/plugins/maps/public/meta.test.js rename to x-pack/plugins/maps/public/util.test.js index fc26bca48032f..47c3d77180077 100644 --- a/x-pack/plugins/maps/public/meta.test.js +++ b/x-pack/plugins/maps/public/util.test.js @@ -6,7 +6,7 @@ */ import { EMSClient } from '@elastic/ems-client'; -import { getEMSClient, getGlyphUrl } from './meta'; +import { getEMSClient, getGlyphUrl } from './util'; jest.mock('@elastic/ems-client'); diff --git a/x-pack/plugins/maps/public/meta.ts b/x-pack/plugins/maps/public/util.ts similarity index 73% rename from x-pack/plugins/maps/public/meta.ts rename to x-pack/plugins/maps/public/util.ts index 11dc033846222..2745f9274f119 100644 --- a/x-pack/plugins/maps/public/meta.ts +++ b/x-pack/plugins/maps/public/util.ts @@ -7,6 +7,10 @@ import { i18n } from '@kbn/i18n'; import { EMSClient, FileLayer, TMSService } from '@elastic/ems-client'; +import { FeatureCollection } from 'geojson'; +// @ts-expect-error +import * as topojson from 'topojson-client'; +import _ from 'lodash'; import fetch from 'node-fetch'; import { @@ -16,6 +20,7 @@ import { EMS_GLYPHS_PATH, EMS_APP_NAME, FONTS_API_PATH, + FORMAT_TYPE, } from '../common/constants'; import { getHttp, @@ -113,3 +118,41 @@ export function getGlyphUrl(): string { export function isRetina(): boolean { return window.devicePixelRatio === 2; } + +export async function fetchGeoJson( + fetchUrl: string, + format: FORMAT_TYPE, + featureCollectionPath: string +): Promise { + let fetchedJson; + try { + const response = await fetch(fetchUrl); + if (!response.ok) { + throw new Error('Request failed'); + } + fetchedJson = await response.json(); + } catch (e) { + throw new Error( + i18n.translate('xpack.maps.util.requestFailedErrorMessage', { + defaultMessage: `Unable to fetch vector shapes from url: {fetchUrl}`, + values: { fetchUrl }, + }) + ); + } + + if (format === FORMAT_TYPE.GEOJSON) { + return fetchedJson; + } + + if (format === FORMAT_TYPE.TOPOJSON) { + const features = _.get(fetchedJson, `objects.${featureCollectionPath}`); + return topojson.feature(fetchedJson, features); + } + + throw new Error( + i18n.translate('xpack.maps.util.formatErrorMessage', { + defaultMessage: `Unable to fetch vector shapes from url: {format}`, + values: { format }, + }) + ); +} diff --git a/x-pack/plugins/maps/server/sample_data/ecommerce_saved_objects.js b/x-pack/plugins/maps/server/sample_data/ecommerce_saved_objects.js index 268794b8a1bce..6e68608c75cef 100644 --- a/x-pack/plugins/maps/server/sample_data/ecommerce_saved_objects.js +++ b/x-pack/plugins/maps/server/sample_data/ecommerce_saved_objects.js @@ -6,6 +6,7 @@ */ import { i18n } from '@kbn/i18n'; +import { emsWorldLayerId } from '../../common'; const layerList = [ { @@ -29,7 +30,7 @@ const layerList = [ alpha: 1, sourceDescriptor: { type: 'EMS_FILE', - id: 'world_countries', + id: emsWorldLayerId, tooltipProperties: ['name', 'iso2'], }, visible: true, diff --git a/x-pack/plugins/maps/server/sample_data/web_logs_saved_objects.js b/x-pack/plugins/maps/server/sample_data/web_logs_saved_objects.js index 31f353fab09ab..86c6c14306faf 100644 --- a/x-pack/plugins/maps/server/sample_data/web_logs_saved_objects.js +++ b/x-pack/plugins/maps/server/sample_data/web_logs_saved_objects.js @@ -6,6 +6,7 @@ */ import { i18n } from '@kbn/i18n'; +import { emsWorldLayerId } from '../../common'; const layerList = [ { @@ -29,7 +30,7 @@ const layerList = [ alpha: 0.5, sourceDescriptor: { type: 'EMS_FILE', - id: 'world_countries', + id: emsWorldLayerId, tooltipProperties: ['name', 'iso2'], }, visible: true, diff --git a/x-pack/plugins/ml/common/index.ts b/x-pack/plugins/ml/common/index.ts index ac21954118e50..c15aa8f414fb1 100644 --- a/x-pack/plugins/ml/common/index.ts +++ b/x-pack/plugins/ml/common/index.ts @@ -5,9 +5,11 @@ * 2.0. */ -export { HitsTotalRelation, SearchResponse7, HITS_TOTAL_RELATION } from './types/es_client'; +export { ES_CLIENT_TOTAL_HITS_RELATION } from './types/es_client'; export { ChartData } from './types/field_histograms'; export { ANOMALY_SEVERITY, ANOMALY_THRESHOLD, SEVERITY_COLORS } from './constants/anomalies'; export { getSeverityColor, getSeverityType } from './util/anomaly_utils'; +export { isPopulatedObject } from './util/object_utils'; +export { isRuntimeMappings } from './util/runtime_field_utils'; export { composeValidators, patternValidator } from './util/validators'; export { extractErrorMessage } from './util/errors'; diff --git a/x-pack/plugins/ml/common/types/es_client.ts b/x-pack/plugins/ml/common/types/es_client.ts index f6db736db2519..249b3c150a082 100644 --- a/x-pack/plugins/ml/common/types/es_client.ts +++ b/x-pack/plugins/ml/common/types/es_client.ts @@ -5,33 +5,24 @@ * 2.0. */ -import type { SearchResponse, ShardsResponse } from 'elasticsearch'; +import { estypes } from '@elastic/elasticsearch'; + import { buildEsQuery } from '../../../../../src/plugins/data/common/es_query/es_query'; import type { DslQuery } from '../../../../../src/plugins/data/common/es_query/kuery'; import type { JsonObject } from '../../../../../src/plugins/kibana_utils/common'; -export const HITS_TOTAL_RELATION = { +import { isPopulatedObject } from '../util/object_utils'; + +export function isMultiBucketAggregate(arg: unknown): arg is estypes.MultiBucketAggregate { + return isPopulatedObject(arg, ['buckets']); +} + +export const ES_CLIENT_TOTAL_HITS_RELATION: Record< + Uppercase, + estypes.TotalHitsRelation +> = { EQ: 'eq', GTE: 'gte', } as const; -export type HitsTotalRelation = typeof HITS_TOTAL_RELATION[keyof typeof HITS_TOTAL_RELATION]; - -// The types specified in `@types/elasticsearch` are out of date and still have `total: number`. -interface SearchResponse7Hits { - hits: SearchResponse['hits']['hits']; - max_score: number; - total: { - value: number; - relation: HitsTotalRelation; - }; -} -export interface SearchResponse7 { - took: number; - timed_out: boolean; - _scroll_id?: string; - _shards: ShardsResponse; - hits: SearchResponse7Hits; - aggregations?: any; -} export type InfluencersFilterQuery = ReturnType | DslQuery | JsonObject; diff --git a/x-pack/plugins/ml/common/types/feature_importance.ts b/x-pack/plugins/ml/common/types/feature_importance.ts index 964ce8c325783..111c8432dd439 100644 --- a/x-pack/plugins/ml/common/types/feature_importance.ts +++ b/x-pack/plugins/ml/common/types/feature_importance.ts @@ -88,15 +88,11 @@ export function isRegressionTotalFeatureImportance( export function isClassificationFeatureImportanceBaseline( baselineData: any ): baselineData is ClassificationFeatureImportanceBaseline { - return ( - isPopulatedObject(baselineData) && - baselineData.hasOwnProperty('classes') && - Array.isArray(baselineData.classes) - ); + return isPopulatedObject(baselineData, ['classes']) && Array.isArray(baselineData.classes); } export function isRegressionFeatureImportanceBaseline( baselineData: any ): baselineData is RegressionFeatureImportanceBaseline { - return isPopulatedObject(baselineData) && baselineData.hasOwnProperty('baseline'); + return isPopulatedObject(baselineData, ['baseline']); } diff --git a/x-pack/plugins/ml/common/util/object_utils.test.ts b/x-pack/plugins/ml/common/util/object_utils.test.ts new file mode 100644 index 0000000000000..8e4196ed4d826 --- /dev/null +++ b/x-pack/plugins/ml/common/util/object_utils.test.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 { isPopulatedObject } from './object_utils'; + +describe('object_utils', () => { + describe('isPopulatedObject()', () => { + it('does not allow numbers', () => { + expect(isPopulatedObject(0)).toBe(false); + }); + it('does not allow strings', () => { + expect(isPopulatedObject('')).toBe(false); + }); + it('does not allow null', () => { + expect(isPopulatedObject(null)).toBe(false); + }); + it('does not allow an empty object', () => { + expect(isPopulatedObject({})).toBe(false); + }); + it('allows an object with an attribute', () => { + expect(isPopulatedObject({ attribute: 'value' })).toBe(true); + }); + it('does not allow an object with a non-existing required attribute', () => { + expect(isPopulatedObject({ attribute: 'value' }, ['otherAttribute'])).toBe(false); + }); + it('allows an object with an existing required attribute', () => { + expect(isPopulatedObject({ attribute: 'value' }, ['attribute'])).toBe(true); + }); + it('allows an object with two existing required attributes', () => { + expect( + isPopulatedObject({ attribute1: 'value1', attribute2: 'value2' }, [ + 'attribute1', + 'attribute2', + ]) + ).toBe(true); + }); + it('does not allow an object with two required attributes where one does not exist', () => { + expect( + isPopulatedObject({ attribute1: 'value1', attribute2: 'value2' }, [ + 'attribute1', + 'otherAttribute', + ]) + ).toBe(false); + }); + }); +}); diff --git a/x-pack/plugins/ml/common/util/object_utils.ts b/x-pack/plugins/ml/common/util/object_utils.ts index 4bbd0c1c2810f..537ee9202b4de 100644 --- a/x-pack/plugins/ml/common/util/object_utils.ts +++ b/x-pack/plugins/ml/common/util/object_utils.ts @@ -5,6 +5,32 @@ * 2.0. */ -export const isPopulatedObject = >(arg: any): arg is T => { - return typeof arg === 'object' && arg !== null && Object.keys(arg).length > 0; +/* + * A type guard to check record like object structures. + * + * Examples: + * - `isPopulatedObject({...})` + * Limits type to Record + * + * - `isPopulatedObject({...}, ['attribute'])` + * Limits type to Record<'attribute', unknown> + * + * - `isPopulatedObject({...})` + * Limits type to a record with keys of the given interface. + * Note that you might want to add keys from the interface to the + * array of requiredAttributes to satisfy runtime requirements. + * Otherwise you'd just satisfy TS requirements but might still + * run into runtime issues. + */ +export const isPopulatedObject = ( + arg: unknown, + requiredAttributes: U[] = [] +): arg is Record => { + return ( + typeof arg === 'object' && + arg !== null && + Object.keys(arg).length > 0 && + (requiredAttributes.length === 0 || + requiredAttributes.every((d) => ({}.hasOwnProperty.call(arg, d)))) + ); }; diff --git a/x-pack/plugins/ml/common/util/runtime_field_utils.test.ts b/x-pack/plugins/ml/common/util/runtime_field_utils.test.ts new file mode 100644 index 0000000000000..1b5e3e18b14f6 --- /dev/null +++ b/x-pack/plugins/ml/common/util/runtime_field_utils.test.ts @@ -0,0 +1,102 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { isRuntimeField, isRuntimeMappings } from './runtime_field_utils'; + +describe('ML runtime field utils', () => { + describe('isRuntimeField()', () => { + it('does not allow numbers', () => { + expect(isRuntimeField(1)).toBe(false); + }); + it('does not allow null', () => { + expect(isRuntimeField(null)).toBe(false); + }); + it('does not allow arrays', () => { + expect(isRuntimeField([])).toBe(false); + }); + it('does not allow empty objects', () => { + expect(isRuntimeField({})).toBe(false); + }); + it('does not allow objects with non-matching attributes', () => { + expect(isRuntimeField({ someAttribute: 'someValue' })).toBe(false); + expect(isRuntimeField({ type: 'wrong-type' })).toBe(false); + expect(isRuntimeField({ type: 'keyword', someAttribute: 'some value' })).toBe(false); + }); + it('allows objects with type attribute only', () => { + expect(isRuntimeField({ type: 'keyword' })).toBe(true); + }); + it('allows objects with both type and script attributes', () => { + expect(isRuntimeField({ type: 'keyword', script: 'some script' })).toBe(true); + }); + }); + + describe('isRuntimeMappings()', () => { + it('does not allow numbers', () => { + expect(isRuntimeMappings(1)).toBe(false); + }); + it('does not allow null', () => { + expect(isRuntimeMappings(null)).toBe(false); + }); + it('does not allow arrays', () => { + expect(isRuntimeMappings([])).toBe(false); + }); + it('does not allow empty objects', () => { + expect(isRuntimeMappings({})).toBe(false); + }); + it('does not allow objects with non-object inner structure', () => { + expect(isRuntimeMappings({ someAttribute: 'someValue' })).toBe(false); + }); + it('does not allow objects with objects with unsupported inner structure', () => { + expect(isRuntimeMappings({ fieldName1: { type: 'keyword' }, fieldName2: 'someValue' })).toBe( + false + ); + expect( + isRuntimeMappings({ + fieldName1: { type: 'keyword' }, + fieldName2: { type: 'keyword', someAttribute: 'some value' }, + }) + ).toBe(false); + expect( + isRuntimeMappings({ + fieldName: { type: 'long', script: 1234 }, + }) + ).toBe(false); + expect( + isRuntimeMappings({ + fieldName: { type: 'long', script: { someAttribute: 'some value' } }, + }) + ).toBe(false); + expect( + isRuntimeMappings({ + fieldName: { type: 'long', script: { source: 1234 } }, + }) + ).toBe(false); + }); + + it('allows object with most basic runtime mapping', () => { + expect(isRuntimeMappings({ fieldName: { type: 'keyword' } })).toBe(true); + }); + it('allows object with multiple most basic runtime mappings', () => { + expect( + isRuntimeMappings({ fieldName1: { type: 'keyword' }, fieldName2: { type: 'keyword' } }) + ).toBe(true); + }); + it('allows object with runtime mappings including scripts', () => { + expect( + isRuntimeMappings({ + fieldName1: { type: 'keyword' }, + fieldName2: { type: 'keyword', script: 'some script as script' }, + }) + ).toBe(true); + expect( + isRuntimeMappings({ + fieldName: { type: 'long', script: { source: 'some script as source' } }, + }) + ).toBe(true); + }); + }); +}); diff --git a/x-pack/plugins/ml/common/util/runtime_field_utils.ts b/x-pack/plugins/ml/common/util/runtime_field_utils.ts index 06a1960cc943b..6d911ecd5d3cb 100644 --- a/x-pack/plugins/ml/common/util/runtime_field_utils.ts +++ b/x-pack/plugins/ml/common/util/runtime_field_utils.ts @@ -6,22 +6,21 @@ */ import { isPopulatedObject } from './object_utils'; -import { RUNTIME_FIELD_TYPES } from '../../../../../src/plugins/data/common/index_patterns'; +import { RUNTIME_FIELD_TYPES } from '../../../../../src/plugins/data/common'; import type { RuntimeField, RuntimeMappings } from '../types/fields'; +type RuntimeType = typeof RUNTIME_FIELD_TYPES[number]; + export function isRuntimeField(arg: unknown): arg is RuntimeField { return ( - isPopulatedObject(arg) && - ((Object.keys(arg).length === 1 && arg.hasOwnProperty('type')) || - (Object.keys(arg).length === 2 && - arg.hasOwnProperty('type') && - arg.hasOwnProperty('script') && + ((isPopulatedObject(arg, ['type']) && Object.keys(arg).length === 1) || + (isPopulatedObject(arg, ['type', 'script']) && + Object.keys(arg).length === 2 && (typeof arg.script === 'string' || - (isPopulatedObject(arg.script) && + (isPopulatedObject(arg.script, ['source']) && Object.keys(arg.script).length === 1 && - arg.script.hasOwnProperty('source') && typeof arg.script.source === 'string')))) && - RUNTIME_FIELD_TYPES.includes(arg.type) + RUNTIME_FIELD_TYPES.includes(arg.type as RuntimeType) ); } diff --git a/x-pack/plugins/ml/public/application/components/data_grid/types.ts b/x-pack/plugins/ml/public/application/components/data_grid/types.ts index 649968f176e18..0af8972f18558 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/types.ts +++ b/x-pack/plugins/ml/public/application/components/data_grid/types.ts @@ -7,6 +7,7 @@ import { Dispatch, SetStateAction } from 'react'; +import { estypes } from '@elastic/elasticsearch'; import { EuiDataGridCellValueElementProps, EuiDataGridPaginationProps, @@ -15,7 +16,6 @@ import { } from '@elastic/eui'; import { Dictionary } from '../../../../common/types/common'; -import { HitsTotalRelation } from '../../../../common/types/es_client'; import { ChartData } from '../../../../common/types/field_histograms'; import { INDEX_STATUS } from '../../data_frame_analytics/common/analytics'; @@ -27,7 +27,7 @@ export type DataGridItem = Record; // `undefined` is used to indicate a non-initialized state. export type ChartsVisible = boolean | undefined; -export type RowCountRelation = HitsTotalRelation | undefined; +export type RowCountRelation = estypes.TotalHitsRelation | undefined; export type IndexPagination = Pick; diff --git a/x-pack/plugins/ml/public/application/components/data_grid/use_data_grid.tsx b/x-pack/plugins/ml/public/application/components/data_grid/use_data_grid.tsx index 31a72a776223e..e62f2eb2f003b 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/use_data_grid.tsx +++ b/x-pack/plugins/ml/public/application/components/data_grid/use_data_grid.tsx @@ -9,7 +9,7 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { EuiDataGridSorting, EuiDataGridColumn } from '@elastic/eui'; -import { HITS_TOTAL_RELATION } from '../../../../common/types/es_client'; +import { ES_CLIENT_TOTAL_HITS_RELATION } from '../../../../common/types/es_client'; import { ChartData } from '../../../../common/types/field_histograms'; import { INDEX_STATUS } from '../../data_frame_analytics/common'; @@ -146,7 +146,7 @@ export const useDataGrid = ( if (chartsVisible === undefined && rowCount > 0 && rowCountRelation !== undefined) { setChartsVisible( rowCount <= COLUMN_CHART_DEFAULT_VISIBILITY_ROWS_THRESHOLED && - rowCountRelation !== HITS_TOTAL_RELATION.GTE + rowCountRelation !== ES_CLIENT_TOTAL_HITS_RELATION.GTE ); } }, [chartsVisible, rowCount, rowCountRelation]); diff --git a/x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix.tsx b/x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix.tsx index 4e9fd3baebe7b..842d5fc1ae87a 100644 --- a/x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix.tsx +++ b/x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix.tsx @@ -7,6 +7,8 @@ import React, { useMemo, useEffect, useState, FC } from 'react'; +import { estypes } from '@elastic/elasticsearch'; + import { EuiCallOut, EuiComboBox, @@ -24,7 +26,6 @@ import { i18n } from '@kbn/i18n'; import { extractErrorMessage } from '../../../../common'; import { stringHash } from '../../../../common/util/string_utils'; -import type { SearchResponse7 } from '../../../../common/types/es_client'; import type { ResultsSearchQuery } from '../../data_frame_analytics/common/analytics'; import { useMlApiContext } from '../../contexts/kibana'; @@ -184,7 +185,7 @@ export const ScatterplotMatrix: FC = ({ } : searchQuery; - const resp: SearchResponse7 = await esSearch({ + const resp: estypes.SearchResponse = await esSearch({ index, body: { fields: queryFields, @@ -198,7 +199,7 @@ export const ScatterplotMatrix: FC = ({ if (!options.didCancel) { const items = resp.hits.hits .map((d) => - getProcessedFields(d.fields, (key: string) => + getProcessedFields(d.fields ?? {}, (key: string) => key.startsWith(`${resultsField}.feature_importance`) ) ) diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/common/get_index_data.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/common/get_index_data.ts index d0bcbd2ff63b4..88f403cdf0c44 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/common/get_index_data.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/common/get_index_data.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { SearchResponse7 } from '../../../../common/types/es_client'; +import type { estypes } from '@elastic/elasticsearch'; import { extractErrorMessage } from '../../../../common/util/errors'; import { EsSorting, UseDataGridReturnType, getProcessedFields } from '../../components/data_grid'; @@ -51,7 +51,7 @@ export const getIndexData = async ( const { pageIndex, pageSize } = pagination; // TODO: remove results_field from `fields` when possible - const resp: SearchResponse7 = await ml.esSearch({ + const resp: estypes.SearchResponse = await ml.esSearch({ index: jobConfig.dest.index, body: { fields: ['*'], @@ -64,11 +64,15 @@ export const getIndexData = async ( }); if (!options.didCancel) { - setRowCount(resp.hits.total.value); - setRowCountRelation(resp.hits.total.relation); + setRowCount(typeof resp.hits.total === 'number' ? resp.hits.total : resp.hits.total.value); + setRowCountRelation( + typeof resp.hits.total === 'number' + ? ('eq' as estypes.TotalHitsRelation) + : resp.hits.total.relation + ); setTableItems( resp.hits.hits.map((d) => - getProcessedFields(d.fields, (key: string) => + getProcessedFields(d.fields ?? {}, (key: string) => key.startsWith(`${jobConfig.dest.results_field}.feature_importance`) ) ) diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/hooks/use_index_data.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/hooks/use_index_data.ts index ecda624c71d98..4552ca34ebbae 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/hooks/use_index_data.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/hooks/use_index_data.ts @@ -7,6 +7,7 @@ import { useEffect, useMemo } from 'react'; +import { estypes } from '@elastic/elasticsearch'; import { EuiDataGridColumn } from '@elastic/eui'; import { CoreSetup } from 'src/core/public'; @@ -26,13 +27,12 @@ import { UseIndexDataReturnType, getProcessedFields, } from '../../../../components/data_grid'; -import type { SearchResponse7 } from '../../../../../../common/types/es_client'; import { extractErrorMessage } from '../../../../../../common/util/errors'; import { INDEX_STATUS } from '../../../common/analytics'; import { ml } from '../../../../services/ml_api_service'; import { getRuntimeFieldsMapping } from '../../../../components/data_grid/common'; -type IndexSearchResponse = SearchResponse7; +type IndexSearchResponse = estypes.SearchResponse; export const useIndexData = ( indexPattern: IndexPattern, @@ -95,9 +95,13 @@ export const useIndexData = ( try { const resp: IndexSearchResponse = await ml.esSearch(esSearchRequest); - const docs = resp.hits.hits.map((d) => getProcessedFields(d.fields)); - setRowCount(resp.hits.total.value); - setRowCountRelation(resp.hits.total.relation); + const docs = resp.hits.hits.map((d) => getProcessedFields(d.fields ?? {})); + setRowCount(typeof resp.hits.total === 'number' ? resp.hits.total : resp.hits.total.value); + setRowCountRelation( + typeof resp.hits.total === 'number' + ? ('eq' as estypes.TotalHitsRelation) + : resp.hits.total.relation + ); setTableItems(docs); setStatus(INDEX_STATUS.LOADED); } catch (e) { diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/expandable_section/expandable_section_results.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/expandable_section/expandable_section_results.tsx index 654af03d102e5..d67473d9d3220 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/expandable_section/expandable_section_results.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/expandable_section/expandable_section_results.tsx @@ -18,7 +18,7 @@ import { isClassificationAnalysis, isRegressionAnalysis, } from '../../../../../../../common/util/analytics_utils'; -import { HITS_TOTAL_RELATION } from '../../../../../../../common/types/es_client'; +import { ES_CLIENT_TOTAL_HITS_RELATION } from '../../../../../../../common/types/es_client'; import { getToastNotifications } from '../../../../../util/dependency_cache'; import { useColorRange, ColorRangeLegend } from '../../../../../components/color_range_legend'; @@ -77,7 +77,7 @@ const getResultsSectionHeaderItems = ( defaultMessage="Total docs" /> ), - value: `${rowCountRelation === HITS_TOTAL_RELATION.GTE ? '>' : ''}${rowCount}`, + value: `${rowCountRelation === ES_CLIENT_TOTAL_HITS_RELATION.GTE ? '>' : ''}${rowCount}`, }, ...(colorRange !== undefined ? [ diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/util/filter_runtime_mappings.ts b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/util/filter_runtime_mappings.ts index bfed2d811e206..5995224ef3254 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/util/filter_runtime_mappings.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/util/filter_runtime_mappings.ts @@ -22,8 +22,10 @@ interface Response { export function filterRuntimeMappings(job: Job, datafeed: Datafeed): Response { if ( - datafeed.runtime_mappings === undefined || - isPopulatedObject(datafeed.runtime_mappings) === false + !( + isPopulatedObject(datafeed, ['runtime_mappings']) && + isPopulatedObject(datafeed.runtime_mappings) + ) ) { return { runtime_mappings: {}, @@ -83,7 +85,7 @@ function findFieldsInJob(job: Job, datafeed: Datafeed) { return [...usedFields]; } -function findFieldsInAgg(obj: Record) { +function findFieldsInAgg(obj: Record) { const fields: string[] = []; Object.entries(obj).forEach(([key, val]) => { if (isPopulatedObject(val)) { diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/common/datafeed_preview_flyout/datafeed_preview.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/common/datafeed_preview_flyout/datafeed_preview.tsx index 916a25271c63b..a4d9293e9369d 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/common/datafeed_preview_flyout/datafeed_preview.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/common/datafeed_preview_flyout/datafeed_preview.tsx @@ -22,6 +22,7 @@ import { MLJobEditor } from '../../../../../jobs_list/components/ml_job_editor'; import { mlJobService } from '../../../../../../services/job_service'; import { ML_DATA_PREVIEW_COUNT } from '../../../../../../../../common/util/job_utils'; import { isPopulatedObject } from '../../../../../../../../common/util/object_utils'; +import { isMultiBucketAggregate } from '../../../../../../../../common/types/es_client'; export const DatafeedPreview: FC<{ combinedJob: CombinedJob | null; @@ -67,7 +68,10 @@ export const DatafeedPreview: FC<{ // the first item under aggregations can be any name if (isPopulatedObject(resp.aggregations)) { const accessor = Object.keys(resp.aggregations)[0]; - data = resp.aggregations[accessor].buckets.slice(0, ML_DATA_PREVIEW_COUNT); + const aggregate = resp.aggregations[accessor]; + if (isMultiBucketAggregate(aggregate)) { + data = aggregate.buckets.slice(0, ML_DATA_PREVIEW_COUNT); + } } setPreviewJsonString(JSON.stringify(data, null, 2)); diff --git a/x-pack/plugins/ml/public/application/services/anomaly_explorer_charts_service.ts b/x-pack/plugins/ml/public/application/services/anomaly_explorer_charts_service.ts index 59b6860cb65b7..72de5d003d4b8 100644 --- a/x-pack/plugins/ml/public/application/services/anomaly_explorer_charts_service.ts +++ b/x-pack/plugins/ml/public/application/services/anomaly_explorer_charts_service.ts @@ -90,11 +90,7 @@ export interface SeriesConfigWithMetadata extends SeriesConfig { } export const isSeriesConfigWithMetadata = (arg: unknown): arg is SeriesConfigWithMetadata => { - return ( - isPopulatedObject(arg) && - {}.hasOwnProperty.call(arg, 'bucketSpanSeconds') && - {}.hasOwnProperty.call(arg, 'detectorLabel') - ); + return isPopulatedObject(arg, ['bucketSpanSeconds', 'detectorLabel']); }; interface ChartRange { diff --git a/x-pack/plugins/ml/public/application/services/job_service.d.ts b/x-pack/plugins/ml/public/application/services/job_service.d.ts index 544d346341591..ceadca12f8757 100644 --- a/x-pack/plugins/ml/public/application/services/job_service.d.ts +++ b/x-pack/plugins/ml/public/application/services/job_service.d.ts @@ -5,7 +5,8 @@ * 2.0. */ -import { SearchResponse } from 'elasticsearch'; +import { estypes } from '@elastic/elasticsearch'; + import { TimeRange } from 'src/plugins/data/common/query/timefilter/types'; import { CombinedJob, Datafeed, Job } from '../../../common/types/anomaly_detection_jobs'; import { Calendar } from '../../../common/types/calendars'; @@ -40,7 +41,7 @@ declare interface JobService { ): Promise; createResultsUrl(jobId: string[], start: number, end: number, location: string): string; getJobAndGroupIds(): Promise; - searchPreview(job: CombinedJob): Promise>; + searchPreview(job: CombinedJob): Promise>; getJob(jobId: string): CombinedJob; loadJobsWrapper(): Promise; } diff --git a/x-pack/plugins/ml/public/embeddables/types.ts b/x-pack/plugins/ml/public/embeddables/types.ts index 05aea1770a415..60355dae5baf4 100644 --- a/x-pack/plugins/ml/public/embeddables/types.ts +++ b/x-pack/plugins/ml/public/embeddables/types.ts @@ -81,8 +81,8 @@ export interface SwimLaneDrilldownContext extends EditSwimlanePanelContext { export function isSwimLaneEmbeddable(arg: unknown): arg is SwimLaneDrilldownContext { return ( - isPopulatedObject(arg) && - arg.hasOwnProperty('embeddable') && + isPopulatedObject(arg, ['embeddable']) && + isPopulatedObject(arg.embeddable, ['type']) && arg.embeddable.type === ANOMALY_SWIMLANE_EMBEDDABLE_TYPE ); } @@ -130,8 +130,8 @@ export function isAnomalyExplorerEmbeddable( arg: unknown ): arg is AnomalyChartsFieldSelectionContext { return ( - isPopulatedObject(arg) && - arg.hasOwnProperty('embeddable') && + isPopulatedObject(arg, ['embeddable']) && + isPopulatedObject(arg.embeddable, ['type']) && arg.embeddable.type === ANOMALY_EXPLORER_CHARTS_EMBEDDABLE_TYPE ); } diff --git a/x-pack/plugins/ml/public/index.ts b/x-pack/plugins/ml/public/index.ts index 9280f4603b343..56b8ca409ac0b 100755 --- a/x-pack/plugins/ml/public/index.ts +++ b/x-pack/plugins/ml/public/index.ts @@ -50,7 +50,7 @@ export { getSeverityType, getFormattedSeverityScore, } from '../common/util/anomaly_utils'; -export { HITS_TOTAL_RELATION } from '../common/types/es_client'; +export { ES_CLIENT_TOTAL_HITS_RELATION } from '../common/types/es_client'; export { ANOMALY_SEVERITY } from '../common'; export { useMlHref, ML_PAGES, MlUrlGenerator } from './ml_url_generator'; diff --git a/x-pack/plugins/ml/server/models/data_frame_analytics/validation.ts b/x-pack/plugins/ml/server/models/data_frame_analytics/validation.ts index 4c79855f39e89..3f0a02f5eaad8 100644 --- a/x-pack/plugins/ml/server/models/data_frame_analytics/validation.ts +++ b/x-pack/plugins/ml/server/models/data_frame_analytics/validation.ts @@ -25,7 +25,6 @@ import { isClassificationAnalysis, } from '../../../common/util/analytics_utils'; import { extractErrorMessage } from '../../../common/util/errors'; -import { SearchResponse7 } from '../../../common'; import { AnalysisConfig, DataFrameAnalyticsConfig, @@ -42,7 +41,7 @@ interface CardinalityAgg { }; } -type ValidationSearchResult = Omit & { +type ValidationSearchResult = Omit & { aggregations: MissingAgg | CardinalityAgg; }; diff --git a/x-pack/plugins/ml/server/models/job_service/new_job/categorization/examples.ts b/x-pack/plugins/ml/server/models/job_service/new_job/categorization/examples.ts index b0ee20763f430..5fecb3d9eb1ec 100644 --- a/x-pack/plugins/ml/server/models/job_service/new_job/categorization/examples.ts +++ b/x-pack/plugins/ml/server/models/job_service/new_job/categorization/examples.ts @@ -5,9 +5,10 @@ * 2.0. */ +import type { estypes } from '@elastic/elasticsearch'; + import { IScopedClusterClient } from 'kibana/server'; import { chunk } from 'lodash'; -import { SearchResponse } from 'elasticsearch'; import { CATEGORY_EXAMPLES_SAMPLE_SIZE } from '../../../../../common/constants/categorization_job'; import { Token, @@ -61,7 +62,7 @@ export function categorizationExamplesProvider({ } } } - const { body } = await asCurrentUser.search>({ + const { body } = await asCurrentUser.search>({ index: indexPatternTitle, size, body: { diff --git a/x-pack/plugins/ml/server/models/job_service/new_job/categorization/top_categories.ts b/x-pack/plugins/ml/server/models/job_service/new_job/categorization/top_categories.ts index 851336056a7f5..82d6f6ca3e103 100644 --- a/x-pack/plugins/ml/server/models/job_service/new_job/categorization/top_categories.ts +++ b/x-pack/plugins/ml/server/models/job_service/new_job/categorization/top_categories.ts @@ -5,13 +5,14 @@ * 2.0. */ -import { SearchResponse } from 'elasticsearch'; +import type { estypes } from '@elastic/elasticsearch'; + import { CategoryId, Category } from '../../../../../common/types/categories'; import type { MlClient } from '../../../../lib/ml_client'; export function topCategoriesProvider(mlClient: MlClient) { async function getTotalCategories(jobId: string): Promise { - const { body } = await mlClient.anomalySearch>( + const { body } = await mlClient.anomalySearch>( { size: 0, body: { @@ -35,12 +36,11 @@ export function topCategoriesProvider(mlClient: MlClient) { }, [] ); - // @ts-ignore total is an object here - return body?.hits?.total?.value ?? 0; + return typeof body.hits.total === 'number' ? body.hits.total : body.hits.total.value; } async function getTopCategoryCounts(jobId: string, numberOfCategories: number) { - const { body } = await mlClient.anomalySearch>( + const { body } = await mlClient.anomalySearch>( { size: 0, body: { diff --git a/x-pack/plugins/ml/server/models/job_validation/job_validation.test.ts b/x-pack/plugins/ml/server/models/job_validation/job_validation.test.ts index 949159b67d33a..64dfb84be8668 100644 --- a/x-pack/plugins/ml/server/models/job_validation/job_validation.test.ts +++ b/x-pack/plugins/ml/server/models/job_validation/job_validation.test.ts @@ -8,13 +8,15 @@ import { IScopedClusterClient } from 'kibana/server'; import { validateJob, ValidateJobPayload } from './job_validation'; -import { HITS_TOTAL_RELATION } from '../../../common/types/es_client'; +import { ES_CLIENT_TOTAL_HITS_RELATION } from '../../../common/types/es_client'; import type { MlClient } from '../../lib/ml_client'; const callAs = { fieldCaps: () => Promise.resolve({ body: { fields: [] } }), search: () => - Promise.resolve({ body: { hits: { total: { value: 1, relation: HITS_TOTAL_RELATION.EQ } } } }), + Promise.resolve({ + body: { hits: { total: { value: 1, relation: ES_CLIENT_TOTAL_HITS_RELATION.EQ } } }, + }), }; const mlClusterClient = ({ diff --git a/x-pack/plugins/ml/server/shared_services/providers/system.ts b/x-pack/plugins/ml/server/shared_services/providers/system.ts index 1e3dcd7de5240..85cd73ba010af 100644 --- a/x-pack/plugins/ml/server/shared_services/providers/system.ts +++ b/x-pack/plugins/ml/server/shared_services/providers/system.ts @@ -5,8 +5,9 @@ * 2.0. */ +import type { estypes } from '@elastic/elasticsearch'; + import { KibanaRequest, SavedObjectsClientContract } from 'kibana/server'; -import { SearchResponse } from 'elasticsearch'; import { MlLicense } from '../../../common/license'; import { CloudSetup } from '../../../../cloud/server'; import { spacesUtilsProvider } from '../../lib/spaces_utils'; @@ -23,7 +24,7 @@ export interface MlSystemProvider { ): { mlCapabilities(): Promise; mlInfo(): Promise; - mlAnomalySearch(searchParams: any, jobIds: string[]): Promise>; + mlAnomalySearch(searchParams: any, jobIds: string[]): Promise>; }; } @@ -69,7 +70,10 @@ export function getMlSystemProvider( }; }); }, - async mlAnomalySearch(searchParams: any, jobIds: string[]): Promise> { + async mlAnomalySearch( + searchParams: any, + jobIds: string[] + ): Promise> { return await getGuards(request, savedObjectsClient) .isFullLicense() .hasMlCapabilities(['canAccessML']) diff --git a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/common.ts b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/common.ts index 29b18ba3f5bf5..65bd6ffd15f5f 100644 --- a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/common.ts +++ b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/common.ts @@ -15,7 +15,7 @@ import { export const ArtifactConstants = { GLOBAL_ALLOWLIST_NAME: 'endpoint-exceptionlist', /** - * Saved objects no longer used for storing artifacts. Value + * Saved objects no longer used for storing artifacts * @deprecated */ SAVED_OBJECT_TYPE: 'endpoint:user-artifact', diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/artifacts/download_artifact.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/artifacts/download_artifact.test.ts index ed945347373e5..c70dd39e17e9e 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/artifacts/download_artifact.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/artifacts/download_artifact.test.ts @@ -171,7 +171,7 @@ describe('test alerts route', () => { // and this entire test file refactored to start using fleet's exposed FleetArtifactClient class. endpointAppContextService! .getManifestManager()! - .getArtifactsClient().getArtifact = jest.fn().mockResolvedValue(soFindResp); + .getArtifactsClient().getArtifact = jest.fn().mockResolvedValue(soFindResp.attributes); [routeConfig, routeHandler] = routerMock.get.mock.calls.find(([{ path }]) => path.startsWith('/api/endpoint/artifacts/download') diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/artifacts/download_artifact.ts b/x-pack/plugins/security_solution/server/endpoint/routes/artifacts/download_artifact.ts index 99a39616195dd..948cd035243bd 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/artifacts/download_artifact.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/artifacts/download_artifact.ts @@ -91,9 +91,9 @@ export function registerDownloadArtifactRoute( return res.notFound({ body: `No artifact found for ${id}` }); } - const bodyBuffer = Buffer.from(artifact.attributes.body, 'base64'); + const bodyBuffer = Buffer.from(artifact.body, 'base64'); cache.set(id, bodyBuffer); - return buildAndValidateResponse(artifact.attributes.identifier, bodyBuffer); + return buildAndValidateResponse(artifact.identifier, bodyBuffer); } } ); diff --git a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/artifact_client.test.ts b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/artifact_client.test.ts index b3f098a969336..1dcac108338bb 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/artifact_client.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/artifact_client.test.ts @@ -5,48 +5,49 @@ * 2.0. */ -import { savedObjectsClientMock } from 'src/core/server/mocks'; -import { ArtifactConstants, getArtifactId } from '../../lib/artifacts'; import { getInternalArtifactMock } from '../../schemas/artifacts/saved_objects.mock'; -import { ArtifactClient } from './artifact_client'; +import { EndpointArtifactClient } from './artifact_client'; +import { createArtifactsClientMock } from '../../../../../fleet/server/mocks'; describe('artifact_client', () => { describe('ArtifactClient sanity checks', () => { + let fleetArtifactClient: ReturnType; + let artifactClient: EndpointArtifactClient; + + beforeEach(() => { + fleetArtifactClient = createArtifactsClientMock(); + artifactClient = new EndpointArtifactClient(fleetArtifactClient); + }); + test('can create ArtifactClient', () => { - const artifactClient = new ArtifactClient(savedObjectsClientMock.create()); - expect(artifactClient).toBeInstanceOf(ArtifactClient); + expect(artifactClient).toBeInstanceOf(EndpointArtifactClient); }); test('can get artifact', async () => { - const savedObjectsClient = savedObjectsClientMock.create(); - const artifactClient = new ArtifactClient(savedObjectsClient); await artifactClient.getArtifact('abcd'); - expect(savedObjectsClient.get).toHaveBeenCalled(); + expect(fleetArtifactClient.listArtifacts).toHaveBeenCalled(); }); test('can create artifact', async () => { - const savedObjectsClient = savedObjectsClientMock.create(); - const artifactClient = new ArtifactClient(savedObjectsClient); - const artifact = await getInternalArtifactMock('linux', 'v1'); + const artifact = await getInternalArtifactMock('linux', 'v1', { compress: true }); await artifactClient.createArtifact(artifact); - expect(savedObjectsClient.create).toHaveBeenCalledWith( - ArtifactConstants.SAVED_OBJECT_TYPE, - { - ...artifact, - created: expect.any(Number), - }, - { id: getArtifactId(artifact) } - ); + expect(fleetArtifactClient.createArtifact).toHaveBeenCalledWith({ + identifier: artifact.identifier, + type: 'exceptionlist', + content: + '{"entries":[{"type":"simple","entries":[{"entries":[{"field":"some.nested.field","operator":"included","type":"exact_cased","value":"some value"}],' + + '"field":"some.parentField","type":"nested"},{"field":"some.not.nested.field","operator":"included","type":"exact_cased","value":"some value"}]},' + + '{"type":"simple","entries":[{"field":"some.other.not.nested.field","operator":"included","type":"exact_cased","value":"some other value"}]}]}', + }); }); test('can delete artifact', async () => { - const savedObjectsClient = savedObjectsClientMock.create(); - const artifactClient = new ArtifactClient(savedObjectsClient); - await artifactClient.deleteArtifact('abcd'); - expect(savedObjectsClient.delete).toHaveBeenCalledWith( - ArtifactConstants.SAVED_OBJECT_TYPE, - 'abcd' - ); + await artifactClient.deleteArtifact('endpoint-trustlist-linux-v1-sha26hash'); + expect(fleetArtifactClient.listArtifacts).toHaveBeenCalledWith({ + kuery: `decoded_sha256: "sha26hash" AND identifier: "endpoint-trustlist-linux-v1"`, + perPage: 1, + }); + expect(fleetArtifactClient.deleteArtifact).toHaveBeenCalledWith('123'); }); }); }); diff --git a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/artifact_client.ts b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/artifact_client.ts index d9a2e86159d6c..ef48ed1dd43f6 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/artifact_client.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/artifact_client.ts @@ -5,64 +5,23 @@ * 2.0. */ -/* eslint-disable max-classes-per-file */ - import { inflate as _inflate } from 'zlib'; import { promisify } from 'util'; -import { SavedObject, SavedObjectsClientContract } from 'src/core/server'; -import { ArtifactConstants, getArtifactId } from '../../lib/artifacts'; -import { - InternalArtifactCompleteSchema, - InternalArtifactCreateSchema, -} from '../../schemas/artifacts'; +import { InternalArtifactCompleteSchema } from '../../schemas/artifacts'; import { Artifact, ArtifactsClientInterface } from '../../../../../fleet/server'; const inflateAsync = promisify(_inflate); export interface EndpointArtifactClientInterface { - getArtifact(id: string): Promise | undefined>; + getArtifact(id: string): Promise; - createArtifact( - artifact: InternalArtifactCompleteSchema - ): Promise>; + createArtifact(artifact: InternalArtifactCompleteSchema): Promise; deleteArtifact(id: string): Promise; } -export class ArtifactClient implements EndpointArtifactClientInterface { - private savedObjectsClient: SavedObjectsClientContract; - - constructor(savedObjectsClient: SavedObjectsClientContract) { - this.savedObjectsClient = savedObjectsClient; - } - - public async getArtifact(id: string): Promise> { - return this.savedObjectsClient.get( - ArtifactConstants.SAVED_OBJECT_TYPE, - id - ); - } - - public async createArtifact( - artifact: InternalArtifactCompleteSchema - ): Promise> { - return this.savedObjectsClient.create( - ArtifactConstants.SAVED_OBJECT_TYPE, - { - ...artifact, - created: Date.now(), - }, - { id: getArtifactId(artifact) } - ); - } - - public async deleteArtifact(id: string) { - await this.savedObjectsClient.delete(ArtifactConstants.SAVED_OBJECT_TYPE, id); - } -} - /** - * Endpoint specific artifact managment client which uses FleetArtifactsClient to persist artifacts + * Endpoint specific artifact management client which uses FleetArtifactsClient to persist artifacts * to the Fleet artifacts index (then used by Fleet Server) */ export class EndpointArtifactClient implements EndpointArtifactClientInterface { @@ -91,15 +50,12 @@ export class EndpointArtifactClient implements EndpointArtifactClientInterface { return; } - // FIXME:PT change method signature so that it returns back only the `InternalArtifactCompleteSchema` - return ({ - attributes: artifacts.items[0], - } as unknown) as SavedObject; + return artifacts.items[0]; } async createArtifact( artifact: InternalArtifactCompleteSchema - ): Promise> { + ): Promise { // FIXME:PT refactor to make this more efficient by passing through the uncompressed artifact content // Artifact `.body` is compressed/encoded. We need it decoded and as a string const artifactContent = await inflateAsync(Buffer.from(artifact.body, 'base64')); @@ -110,15 +66,13 @@ export class EndpointArtifactClient implements EndpointArtifactClientInterface { type: this.parseArtifactId(artifact.identifier).type, }); - return ({ - attributes: createdArtifact, - } as unknown) as SavedObject; + return createdArtifact; } async deleteArtifact(id: string) { // Ignoring the `id` not being in the type until we can refactor the types in endpoint. // @ts-ignore - const artifactId = (await this.getArtifact(id)).attributes?.id; + const artifactId = (await this.getArtifact(id))?.id!; return this.fleetArtifacts.deleteArtifact(artifactId); } } diff --git a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.mock.ts b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.mock.ts index ca0088e834c3a..ececb425af657 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.mock.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.mock.ts @@ -20,8 +20,7 @@ import { getMockArtifactsWithDiff, getEmptyMockArtifacts, } from '../../../lib/artifacts/mocks'; -import { ArtifactClient } from '../artifact_client'; -import { getManifestClientMock } from '../mocks'; +import { createEndpointArtifactClientMock, getManifestClientMock } from '../mocks'; import { ManifestManager, ManifestManagerContext } from './manifest_manager'; export const createExceptionListResponse = (data: ExceptionListItemSchema[], total?: number) => ({ @@ -84,7 +83,7 @@ export const buildManifestManagerContextMock = ( return { ...fullOpts, - artifactClient: new ArtifactClient(fullOpts.savedObjectsClient), + artifactClient: createEndpointArtifactClientMock(), logger: loggingSystemMock.create().get() as jest.Mocked, }; }; diff --git a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts index a4efdbc75fb16..423cd4fddd0aa 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts @@ -6,7 +6,6 @@ */ import { inflateSync } from 'zlib'; -import { SavedObjectsErrorHelpers } from 'src/core/server'; import { savedObjectsClientMock } from 'src/core/server/mocks'; import { ENDPOINT_LIST_ID, ENDPOINT_TRUSTED_APPS_LIST_ID } from '../../../../../../lists/common'; import { getExceptionListItemSchemaMock } from '../../../../../../lists/common/schemas/response/exception_list_item_schema.mock'; @@ -23,7 +22,6 @@ import { toArtifactRecords, } from '../../../lib/artifacts/mocks'; import { - ArtifactConstants, ManifestConstants, getArtifactId, isCompressed, @@ -37,6 +35,7 @@ import { } from './manifest_manager.mock'; import { ManifestManager } from './manifest_manager'; +import { EndpointArtifactClientInterface } from '../artifact_client'; const uncompressData = async (data: Buffer) => JSON.parse(await inflateSync(data).toString()); @@ -145,9 +144,8 @@ describe('ManifestManager', () => { test('Retrieves non empty manifest successfully', async () => { const savedObjectsClient = savedObjectsClientMock.create(); - const manifestManager = new ManifestManager( - buildManifestManagerContextMock({ savedObjectsClient }) - ); + const manifestManagerContext = buildManifestManagerContextMock({ savedObjectsClient }); + const manifestManager = new ManifestManager(manifestManagerContext); savedObjectsClient.get = jest .fn() @@ -169,13 +167,17 @@ describe('ManifestManager', () => { }, version: '2.0.0', }; - } else if (objectType === ArtifactConstants.SAVED_OBJECT_TYPE) { - return { attributes: ARTIFACTS_BY_ID[id], version: '2.1.1' }; } else { return null; } }); + (manifestManagerContext.artifactClient as jest.Mocked).getArtifact.mockImplementation( + async (id) => { + return ARTIFACTS_BY_ID[id]; + } + ); + const manifest = await manifestManager.getLastComputedManifest(); expect(manifest?.getSchemaVersion()).toStrictEqual('v1'); @@ -418,8 +420,6 @@ describe('ManifestManager', () => { const context = buildManifestManagerContextMock({}); const manifestManager = new ManifestManager(context); - context.savedObjectsClient.delete = jest.fn().mockResolvedValue({}); - await expect( manifestManager.deleteArtifacts([ ARTIFACT_ID_EXCEPTIONS_MACOS, @@ -427,32 +427,27 @@ describe('ManifestManager', () => { ]) ).resolves.toStrictEqual([]); - expect(context.savedObjectsClient.delete).toHaveBeenNthCalledWith( + expect(context.artifactClient.deleteArtifact).toHaveBeenNthCalledWith( 1, - ArtifactConstants.SAVED_OBJECT_TYPE, ARTIFACT_ID_EXCEPTIONS_MACOS ); - expect(context.savedObjectsClient.delete).toHaveBeenNthCalledWith( + expect(context.artifactClient.deleteArtifact).toHaveBeenNthCalledWith( 2, - ArtifactConstants.SAVED_OBJECT_TYPE, ARTIFACT_ID_EXCEPTIONS_WINDOWS ); }); test('Returns errors for partial failures', async () => { const context = buildManifestManagerContextMock({}); + const artifactClient = context.artifactClient as jest.Mocked; const manifestManager = new ManifestManager(context); const error = new Error(); - context.savedObjectsClient.delete = jest - .fn() - .mockImplementation(async (type: string, id: string) => { - if (id === ARTIFACT_ID_EXCEPTIONS_WINDOWS) { - throw error; - } else { - return {}; - } - }); + artifactClient.deleteArtifact.mockImplementation(async (id) => { + if (id === ARTIFACT_ID_EXCEPTIONS_WINDOWS) { + throw error; + } + }); await expect( manifestManager.deleteArtifacts([ @@ -461,46 +456,35 @@ describe('ManifestManager', () => { ]) ).resolves.toStrictEqual([error]); - expect(context.savedObjectsClient.delete).toHaveBeenCalledTimes(2); - expect(context.savedObjectsClient.delete).toHaveBeenNthCalledWith( + expect(artifactClient.deleteArtifact).toHaveBeenCalledTimes(2); + expect(artifactClient.deleteArtifact).toHaveBeenNthCalledWith( 1, - ArtifactConstants.SAVED_OBJECT_TYPE, ARTIFACT_ID_EXCEPTIONS_MACOS ); - expect(context.savedObjectsClient.delete).toHaveBeenNthCalledWith( + expect(artifactClient.deleteArtifact).toHaveBeenNthCalledWith( 2, - ArtifactConstants.SAVED_OBJECT_TYPE, ARTIFACT_ID_EXCEPTIONS_WINDOWS ); }); }); describe('pushArtifacts', () => { - test('Successfully invokes saved objects client and stores in the cache', async () => { + test('Successfully invokes artifactClient and stores in the cache', async () => { const context = buildManifestManagerContextMock({}); + const artifactClient = context.artifactClient as jest.Mocked; const manifestManager = new ManifestManager(context); - context.savedObjectsClient.create = jest - .fn() - .mockImplementation((type: string, artifact: InternalArtifactCompleteSchema) => artifact); - await expect( manifestManager.pushArtifacts([ARTIFACT_EXCEPTIONS_MACOS, ARTIFACT_EXCEPTIONS_WINDOWS]) ).resolves.toStrictEqual([]); - expect(context.savedObjectsClient.create).toHaveBeenCalledTimes(2); - expect(context.savedObjectsClient.create).toHaveBeenNthCalledWith( - 1, - ArtifactConstants.SAVED_OBJECT_TYPE, - { ...ARTIFACT_EXCEPTIONS_MACOS, created: expect.anything() }, - { id: ARTIFACT_ID_EXCEPTIONS_MACOS } - ); - expect(context.savedObjectsClient.create).toHaveBeenNthCalledWith( - 2, - ArtifactConstants.SAVED_OBJECT_TYPE, - { ...ARTIFACT_EXCEPTIONS_WINDOWS, created: expect.anything() }, - { id: ARTIFACT_ID_EXCEPTIONS_WINDOWS } - ); + expect(artifactClient.createArtifact).toHaveBeenCalledTimes(2); + expect(artifactClient.createArtifact).toHaveBeenNthCalledWith(1, { + ...ARTIFACT_EXCEPTIONS_MACOS, + }); + expect(artifactClient.createArtifact).toHaveBeenNthCalledWith(2, { + ...ARTIFACT_EXCEPTIONS_WINDOWS, + }); expect( await uncompressData(context.cache.get(getArtifactId(ARTIFACT_EXCEPTIONS_MACOS))!) ).toStrictEqual(await uncompressArtifact(ARTIFACT_EXCEPTIONS_MACOS)); @@ -511,19 +495,20 @@ describe('ManifestManager', () => { test('Returns errors for partial failures', async () => { const context = buildManifestManagerContextMock({}); + const artifactClient = context.artifactClient as jest.Mocked; const manifestManager = new ManifestManager(context); const error = new Error(); const { body, ...incompleteArtifact } = ARTIFACT_TRUSTED_APPS_MACOS; - context.savedObjectsClient.create = jest - .fn() - .mockImplementation(async (type: string, artifact: InternalArtifactCompleteSchema) => { + artifactClient.createArtifact.mockImplementation( + async (artifact: InternalArtifactCompleteSchema) => { if (getArtifactId(artifact) === ARTIFACT_ID_EXCEPTIONS_WINDOWS) { throw error; } else { return artifact; } - }); + } + ); await expect( manifestManager.pushArtifacts([ @@ -536,45 +521,15 @@ describe('ManifestManager', () => { new Error(`Incomplete artifact: ${ARTIFACT_ID_TRUSTED_APPS_MACOS}`), ]); - expect(context.savedObjectsClient.create).toHaveBeenCalledTimes(2); - expect(context.savedObjectsClient.create).toHaveBeenNthCalledWith( - 1, - ArtifactConstants.SAVED_OBJECT_TYPE, - { ...ARTIFACT_EXCEPTIONS_MACOS, created: expect.anything() }, - { id: ARTIFACT_ID_EXCEPTIONS_MACOS } - ); + expect(artifactClient.createArtifact).toHaveBeenCalledTimes(2); + expect(artifactClient.createArtifact).toHaveBeenNthCalledWith(1, { + ...ARTIFACT_EXCEPTIONS_MACOS, + }); expect( await uncompressData(context.cache.get(getArtifactId(ARTIFACT_EXCEPTIONS_MACOS))!) ).toStrictEqual(await uncompressArtifact(ARTIFACT_EXCEPTIONS_MACOS)); expect(context.cache.get(getArtifactId(ARTIFACT_EXCEPTIONS_WINDOWS))).toBeUndefined(); }); - - test('Tolerates saved objects client conflict', async () => { - const context = buildManifestManagerContextMock({}); - const manifestManager = new ManifestManager(context); - - context.savedObjectsClient.create = jest - .fn() - .mockRejectedValue( - SavedObjectsErrorHelpers.createConflictError( - ArtifactConstants.SAVED_OBJECT_TYPE, - ARTIFACT_ID_EXCEPTIONS_MACOS - ) - ); - - await expect( - manifestManager.pushArtifacts([ARTIFACT_EXCEPTIONS_MACOS]) - ).resolves.toStrictEqual([]); - - expect(context.savedObjectsClient.create).toHaveBeenCalledTimes(1); - expect(context.savedObjectsClient.create).toHaveBeenNthCalledWith( - 1, - ArtifactConstants.SAVED_OBJECT_TYPE, - { ...ARTIFACT_EXCEPTIONS_MACOS, created: expect.anything() }, - { id: ARTIFACT_ID_EXCEPTIONS_MACOS } - ); - expect(context.cache.get(getArtifactId(ARTIFACT_EXCEPTIONS_MACOS))).toBeUndefined(); - }); }); describe('commit', () => { diff --git a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts index e219da38931da..9ed17686fd2bc 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts @@ -32,7 +32,7 @@ import { InternalArtifactCompleteSchema, internalArtifactCompleteSchema, } from '../../../schemas/artifacts'; -import { ArtifactClient } from '../artifact_client'; +import { EndpointArtifactClientInterface } from '../artifact_client'; import { ManifestClient } from '../manifest_client'; interface ArtifactsBuildResult { @@ -76,7 +76,7 @@ const iterateAllListItems = async ( export interface ManifestManagerContext { savedObjectsClient: SavedObjectsClientContract; - artifactClient: ArtifactClient; + artifactClient: EndpointArtifactClientInterface; exceptionListClient: ExceptionListClient; packagePolicyService: PackagePolicyServiceInterface; logger: Logger; @@ -92,7 +92,7 @@ const manifestsEqual = (manifest1: ManifestSchema, manifest2: ManifestSchema) => isEqual(new Set(getArtifactIds(manifest1)), new Set(getArtifactIds(manifest2))); export class ManifestManager { - protected artifactClient: ArtifactClient; + protected artifactClient: EndpointArtifactClientInterface; protected exceptionListClient: ExceptionListClient; protected packagePolicyService: PackagePolicyServiceInterface; protected savedObjectsClient: SavedObjectsClientContract; @@ -290,10 +290,13 @@ export class ManifestManager { ); for (const entry of manifestSo.attributes.artifacts) { - manifest.addEntry( - (await this.artifactClient.getArtifact(entry.artifactId)).attributes, - entry.policyId - ); + const artifact = await this.artifactClient.getArtifact(entry.artifactId); + + if (!artifact) { + throw new Error(`artifact id [${entry.artifactId}] not found!`); + } + + manifest.addEntry(artifact, entry.policyId); } return manifest; @@ -462,7 +465,7 @@ export class ManifestManager { }); } - public getArtifactsClient(): ArtifactClient { + public getArtifactsClient(): EndpointArtifactClientInterface { return this.artifactClient; } } diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/sender.test.ts b/x-pack/plugins/security_solution/server/lib/telemetry/sender.test.ts index d5edd4678a9a2..b32d2a6542f4a 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/sender.test.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/sender.test.ts @@ -41,6 +41,7 @@ describe('TelemetryEventsSender', () => { }, file: { size: 3, + created: 0, path: 'X', test: 'me', another: 'nope', @@ -66,6 +67,20 @@ describe('TelemetryEventsSender', () => { }, something_else: 'nope', }, + process: { + name: 'foo.exe', + nope: 'nope', + executable: null, // null fields are never allowlisted + }, + Target: { + process: { + name: 'bar.exe', + nope: 'nope', + thread: { + id: 1234, + }, + }, + }, }, ]; @@ -85,6 +100,7 @@ describe('TelemetryEventsSender', () => { }, file: { size: 3, + created: 0, path: 'X', Ext: { code_signature: { @@ -106,6 +122,17 @@ describe('TelemetryEventsSender', () => { name: 'windows', }, }, + process: { + name: 'foo.exe', + }, + Target: { + process: { + name: 'bar.exe', + thread: { + id: 1234, + }, + }, + }, }, ]); }); diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/sender.ts b/x-pack/plugins/security_solution/server/lib/telemetry/sender.ts index 114cf5d2d3425..7d723c578e3d0 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/sender.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/sender.ts @@ -293,10 +293,46 @@ interface AllowlistFields { [key: string]: boolean | AllowlistFields; } +// Allow list process fields within events. This includes "process" and "Target.process".' +/* eslint-disable @typescript-eslint/naming-convention */ +const allowlistProcessFields: AllowlistFields = { + name: true, + executable: true, + command_line: true, + hash: true, + pid: true, + uptime: true, + Ext: { + architecture: true, + code_signature: true, + dll: true, + token: { + integrity_level_name: true, + }, + }, + parent: { + name: true, + executable: true, + command_line: true, + hash: true, + Ext: { + architecture: true, + code_signature: true, + dll: true, + token: { + integrity_level_name: true, + }, + }, + uptime: true, + pid: true, + ppid: true, + }, + thread: true, +}; + // Allow list for the data we include in the events. True means that it is deep-cloned // blindly. Object contents means that we only copy the fields that appear explicitly in // the sub-object. -/* eslint-disable @typescript-eslint/naming-convention */ const allowlistEventFields: AllowlistFields = { '@timestamp': true, agent: true, @@ -332,127 +368,9 @@ const allowlistEventFields: AllowlistFields = { host: { os: true, }, - process: { - name: true, - executable: true, - command_line: true, - hash: true, - pid: true, - uptime: true, - Ext: { - architecture: true, - code_signature: true, - dll: true, - token: { - integrity_level_name: true, - }, - }, - parent: { - name: true, - executable: true, - command_line: true, - hash: true, - Ext: { - architecture: true, - code_signature: true, - dll: true, - token: { - integrity_level_name: true, - }, - }, - uptime: true, - pid: true, - ppid: true, - }, - token: { - integrity_level_name: true, - }, - thread: true, - }, + process: allowlistProcessFields, Target: { - process: { - Ext: { - architecture: true, - code_signature: true, - dll: true, - token: { - integrity_level_name: true, - }, - }, - parent: { - process: { - Ext: { - architecture: true, - code_signature: true, - dll: true, - token: { - integrity_level_name: true, - }, - }, - }, - }, - thread: { - Ext: { - call_stack: true, - start_address: true, - start_address_allocation_offset: true, - start_address_bytes: true, - start_address_bytes_disasm: true, - start_address_bytes_disasm_hash: true, - start_address_details: { - allocation_base: true, - allocation_protection: true, - allocation_size: true, - allocation_type: true, - bytes_address: true, - bytes_allocation_offset: true, - bytes_compressed: true, - bytes_compressed_present: true, - mapped_pe: { - Ext: { - code_signature: { - status: true, - subject_name: true, - trusted: true, - }, - legal_copyright: true, - product_version: true, - }, - company: true, - description: true, - file_version: true, - imphash: true, - original_file_name: true, - product: true, - }, - mapped_pe_path: true, - memory_pe: { - Ext: { - code_signature: { - status: true, - subject_name: true, - trusted: true, - }, - legal_copyright: true, - product_version: true, - }, - company: true, - description: true, - file_version: true, - imphash: true, - original_file_name: true, - product: true, - }, - memory_pe_detected: true, - region_base: true, - region_protection: true, - region_size: true, - region_state: true, - strings: true, - }, - }, - }, - }, + process: allowlistProcessFields, }, }; @@ -462,7 +380,7 @@ export function copyAllowlistedFields( ): TelemetryEvent { return Object.entries(allowlist).reduce((newEvent, [allowKey, allowValue]) => { const eventValue = event[allowKey]; - if (eventValue) { + if (eventValue !== null && eventValue !== undefined) { if (allowValue === true) { return { ...newEvent, [allowKey]: eventValue }; } else if (typeof allowValue === 'object' && typeof eventValue === 'object') { diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index 21724e065cb99..04f98e53ea9a3 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -59,7 +59,7 @@ import { registerEndpointRoutes } from './endpoint/routes/metadata'; import { registerLimitedConcurrencyRoutes } from './endpoint/routes/limited_concurrency'; import { registerResolverRoutes } from './endpoint/routes/resolver'; import { registerPolicyRoutes } from './endpoint/routes/policy'; -import { ArtifactClient, EndpointArtifactClient, ManifestManager } from './endpoint/services'; +import { EndpointArtifactClient, ManifestManager } from './endpoint/services'; import { EndpointAppContextService } from './endpoint/endpoint_app_context_services'; import { EndpointAppContext } from './endpoint/types'; import { registerDownloadArtifactRoute } from './endpoint/routes/artifacts'; @@ -352,9 +352,9 @@ export class Plugin implements IPlugin = ({ > , ], diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/home/repository_list/repository_table/repository_table.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/home/repository_list/repository_table/repository_table.tsx index 45c62f7fc57c1..3e605ade5f3c3 100644 --- a/x-pack/plugins/snapshot_restore/public/application/sections/home/repository_list/repository_table/repository_table.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/sections/home/repository_list/repository_table/repository_table.tsx @@ -261,7 +261,7 @@ export const RepositoryTable: React.FunctionComponent = ({ > , ], diff --git a/x-pack/plugins/transform/common/api_schemas/type_guards.ts b/x-pack/plugins/transform/common/api_schemas/type_guards.ts index 476e2bad853c9..4b66de9be20d2 100644 --- a/x-pack/plugins/transform/common/api_schemas/type_guards.ts +++ b/x-pack/plugins/transform/common/api_schemas/type_guards.ts @@ -5,10 +5,10 @@ * 2.0. */ -import type { SearchResponse7 } from '../../../ml/common'; +import type { estypes } from '@elastic/elasticsearch'; import type { EsIndex } from '../types/es_index'; -import { isPopulatedObject } from '../utils/object_utils'; +import { isPopulatedObject } from '../shared_imports'; // To be able to use the type guards on the client side, we need to make sure we don't import // the code of '@kbn/config-schema' but just its types, otherwise the client side code will @@ -28,20 +28,13 @@ import type { GetTransformsStatsResponseSchema } from './transforms_stats'; import type { PostTransformsUpdateResponseSchema } from './update_transforms'; const isGenericResponseSchema = (arg: any): arg is T => { - return ( - isPopulatedObject(arg) && - {}.hasOwnProperty.call(arg, 'count') && - {}.hasOwnProperty.call(arg, 'transforms') && - Array.isArray(arg.transforms) - ); + return isPopulatedObject(arg, ['count', 'transforms']) && Array.isArray(arg.transforms); }; export const isGetTransformNodesResponseSchema = ( arg: unknown ): arg is GetTransformNodesResponseSchema => { - return ( - isPopulatedObject(arg) && {}.hasOwnProperty.call(arg, 'count') && typeof arg.count === 'number' - ); + return isPopulatedObject(arg, ['count']) && typeof arg.count === 'number'; }; export const isGetTransformsResponseSchema = (arg: unknown): arg is GetTransformsResponseSchema => { @@ -59,7 +52,7 @@ export const isDeleteTransformsResponseSchema = ( ): arg is DeleteTransformsResponseSchema => { return ( isPopulatedObject(arg) && - Object.values(arg).every((d) => ({}.hasOwnProperty.call(d, 'transformDeleted'))) + Object.values(arg).every((d) => isPopulatedObject(d, ['transformDeleted'])) ); }; @@ -67,8 +60,22 @@ export const isEsIndices = (arg: unknown): arg is EsIndex[] => { return Array.isArray(arg); }; -export const isEsSearchResponse = (arg: unknown): arg is SearchResponse7 => { - return isPopulatedObject(arg) && {}.hasOwnProperty.call(arg, 'hits'); +export const isEsSearchResponse = (arg: unknown): arg is estypes.SearchResponse => { + return isPopulatedObject(arg, ['hits']); +}; + +type SearchResponseWithAggregations = Required> & + estypes.SearchResponse; +export const isEsSearchResponseWithAggregations = ( + arg: unknown +): arg is SearchResponseWithAggregations => { + return isEsSearchResponse(arg) && {}.hasOwnProperty.call(arg, 'aggregations'); +}; + +export const isMultiBucketAggregate = ( + arg: unknown +): arg is estypes.MultiBucketAggregate => { + return isPopulatedObject(arg, ['buckets']); }; export const isFieldHistogramsResponseSchema = ( @@ -87,9 +94,7 @@ export const isPostTransformsPreviewResponseSchema = ( arg: unknown ): arg is PostTransformsPreviewResponseSchema => { return ( - isPopulatedObject(arg) && - {}.hasOwnProperty.call(arg, 'generated_dest_index') && - {}.hasOwnProperty.call(arg, 'preview') && + isPopulatedObject(arg, ['generated_dest_index', 'preview']) && typeof arg.generated_dest_index !== undefined && Array.isArray(arg.preview) ); @@ -98,21 +103,19 @@ export const isPostTransformsPreviewResponseSchema = ( export const isPostTransformsUpdateResponseSchema = ( arg: unknown ): arg is PostTransformsUpdateResponseSchema => { - return isPopulatedObject(arg) && {}.hasOwnProperty.call(arg, 'id') && typeof arg.id === 'string'; + return isPopulatedObject(arg, ['id']) && typeof arg.id === 'string'; }; export const isPutTransformsResponseSchema = (arg: unknown): arg is PutTransformsResponseSchema => { return ( - isPopulatedObject(arg) && - {}.hasOwnProperty.call(arg, 'transformsCreated') && - {}.hasOwnProperty.call(arg, 'errors') && + isPopulatedObject(arg, ['transformsCreated', 'errors']) && Array.isArray(arg.transformsCreated) && Array.isArray(arg.errors) ); }; const isGenericSuccessResponseSchema = (arg: unknown) => - isPopulatedObject(arg) && Object.values(arg).every((d) => ({}.hasOwnProperty.call(d, 'success'))); + isPopulatedObject(arg) && Object.values(arg).every((d) => isPopulatedObject(d, ['success'])); export const isStartTransformsResponseSchema = ( arg: unknown diff --git a/x-pack/plugins/transform/common/shared_imports.ts b/x-pack/plugins/transform/common/shared_imports.ts index 3062c7ab8d23c..38cfb6bc457f1 100644 --- a/x-pack/plugins/transform/common/shared_imports.ts +++ b/x-pack/plugins/transform/common/shared_imports.ts @@ -5,10 +5,10 @@ * 2.0. */ -export type { HitsTotalRelation, SearchResponse7 } from '../../ml/common'; export { composeValidators, + isPopulatedObject, + isRuntimeMappings, patternValidator, ChartData, - HITS_TOTAL_RELATION, } from '../../ml/common'; diff --git a/x-pack/plugins/transform/common/types/index_pattern.ts b/x-pack/plugins/transform/common/types/index_pattern.ts index bab31b67b2b61..0485de8982e1a 100644 --- a/x-pack/plugins/transform/common/types/index_pattern.ts +++ b/x-pack/plugins/transform/common/types/index_pattern.ts @@ -7,17 +7,17 @@ import type { IndexPattern } from '../../../../../src/plugins/data/common'; -import { isPopulatedObject } from '../utils/object_utils'; +import { isPopulatedObject } from '../shared_imports'; // Custom minimal type guard for IndexPattern to check against the attributes used in transforms code. export function isIndexPattern(arg: any): arg is IndexPattern { return ( - isPopulatedObject(arg) && - 'getComputedFields' in arg && - typeof arg.getComputedFields === 'function' && - {}.hasOwnProperty.call(arg, 'title') && + isPopulatedObject(arg, ['title', 'fields']) && + // `getComputedFields` is inherited, so it's not possible to + // check with `hasOwnProperty` which is used by isPopulatedObject() + 'getComputedFields' in (arg as IndexPattern) && + typeof (arg as IndexPattern).getComputedFields === 'function' && typeof arg.title === 'string' && - {}.hasOwnProperty.call(arg, 'fields') && Array.isArray(arg.fields) ); } diff --git a/x-pack/plugins/transform/common/types/transform.ts b/x-pack/plugins/transform/common/types/transform.ts index 600808c475fd1..f1e7efdadca9d 100644 --- a/x-pack/plugins/transform/common/types/transform.ts +++ b/x-pack/plugins/transform/common/types/transform.ts @@ -7,7 +7,7 @@ import { EuiComboBoxOptionOption } from '@elastic/eui/src/components/combo_box/types'; import type { LatestFunctionConfig, PutTransformsRequestSchema } from '../api_schemas/transforms'; -import { isPopulatedObject } from '../utils/object_utils'; +import { isPopulatedObject } from '../shared_imports'; import { PivotGroupByDict } from './pivot_group_by'; import { PivotAggDict } from './pivot_aggs'; @@ -46,11 +46,11 @@ export type TransformLatestConfig = Omit & { export type TransformConfigUnion = TransformPivotConfig | TransformLatestConfig; export function isPivotTransform(transform: unknown): transform is TransformPivotConfig { - return isPopulatedObject(transform) && transform.hasOwnProperty('pivot'); + return isPopulatedObject(transform, ['pivot']); } export function isLatestTransform(transform: unknown): transform is TransformLatestConfig { - return isPopulatedObject(transform) && transform.hasOwnProperty('latest'); + return isPopulatedObject(transform, ['latest']); } export interface LatestFunctionConfigUI { diff --git a/x-pack/plugins/transform/common/types/transform_stats.ts b/x-pack/plugins/transform/common/types/transform_stats.ts index 03e6b2e403b69..00ffa40b84d3b 100644 --- a/x-pack/plugins/transform/common/types/transform_stats.ts +++ b/x-pack/plugins/transform/common/types/transform_stats.ts @@ -6,7 +6,7 @@ */ import { TransformState, TRANSFORM_STATE } from '../constants'; -import { isPopulatedObject } from '../utils/object_utils'; +import { isPopulatedObject } from '../shared_imports'; import { TransformId } from './transform'; export interface TransformStats { @@ -61,7 +61,5 @@ function isTransformState(arg: unknown): arg is TransformState { } export function isTransformStats(arg: unknown): arg is TransformStats { - return ( - isPopulatedObject(arg) && {}.hasOwnProperty.call(arg, 'state') && isTransformState(arg.state) - ); + return isPopulatedObject(arg, ['state']) && isTransformState(arg.state); } diff --git a/x-pack/plugins/transform/common/utils/errors.ts b/x-pack/plugins/transform/common/utils/errors.ts index 46ff3f9165c00..2aff8f332b130 100644 --- a/x-pack/plugins/transform/common/utils/errors.ts +++ b/x-pack/plugins/transform/common/utils/errors.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { isPopulatedObject } from './object_utils'; +import { isPopulatedObject } from '../shared_imports'; export interface ErrorResponse { body: { @@ -18,7 +18,11 @@ export interface ErrorResponse { } export function isErrorResponse(arg: unknown): arg is ErrorResponse { - return isPopulatedObject(arg) && isPopulatedObject(arg.body) && arg?.body?.message !== undefined; + return ( + isPopulatedObject(arg, ['body']) && + isPopulatedObject(arg.body, ['message']) && + arg.body.message !== undefined + ); } export function getErrorMessage(error: unknown) { @@ -26,7 +30,7 @@ export function getErrorMessage(error: unknown) { return `${error.body.error}: ${error.body.message}`; } - if (isPopulatedObject(error) && typeof error.message === 'string') { + if (isPopulatedObject(error, ['message']) && typeof error.message === 'string') { return error.message; } diff --git a/x-pack/plugins/transform/common/utils/object_utils.test.ts b/x-pack/plugins/transform/common/utils/object_utils.test.ts index 5b354b9b27589..c99adf6b6d189 100644 --- a/x-pack/plugins/transform/common/utils/object_utils.test.ts +++ b/x-pack/plugins/transform/common/utils/object_utils.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { getNestedProperty, isPopulatedObject } from './object_utils'; +import { getNestedProperty } from './object_utils'; describe('object_utils', () => { test('getNestedProperty()', () => { @@ -68,12 +68,4 @@ describe('object_utils', () => { expect(typeof test11).toBe('number'); expect(test11).toBe(0); }); - - test('isPopulatedObject()', () => { - expect(isPopulatedObject(0)).toBe(false); - expect(isPopulatedObject('')).toBe(false); - expect(isPopulatedObject(null)).toBe(false); - expect(isPopulatedObject({})).toBe(false); - expect(isPopulatedObject({ attribute: 'value' })).toBe(true); - }); }); diff --git a/x-pack/plugins/transform/common/utils/object_utils.ts b/x-pack/plugins/transform/common/utils/object_utils.ts index a573535da6b4f..605af48914360 100644 --- a/x-pack/plugins/transform/common/utils/object_utils.ts +++ b/x-pack/plugins/transform/common/utils/object_utils.ts @@ -51,7 +51,3 @@ export const setNestedProperty = (obj: Record, accessor: string, va return obj; }; - -export const isPopulatedObject = >(arg: unknown): arg is T => { - return typeof arg === 'object' && arg !== null && Object.keys(arg).length > 0; -}; diff --git a/x-pack/plugins/transform/public/__mocks__/shared_imports.ts b/x-pack/plugins/transform/public/__mocks__/shared_imports.ts index 00a92865789ff..ae072e6666e4a 100644 --- a/x-pack/plugins/transform/public/__mocks__/shared_imports.ts +++ b/x-pack/plugins/transform/public/__mocks__/shared_imports.ts @@ -16,4 +16,4 @@ export const useRequest = jest.fn(() => ({ export const createSavedSearchesLoader = jest.fn(); // just passing through the reimports -export { getMlSharedImports, HITS_TOTAL_RELATION } from '../../../ml/public'; +export { getMlSharedImports, ES_CLIENT_TOTAL_HITS_RELATION } from '../../../ml/public'; diff --git a/x-pack/plugins/transform/public/app/common/pivot_aggs.ts b/x-pack/plugins/transform/public/app/common/pivot_aggs.ts index 905b40f16f7fb..03e06d36f9319 100644 --- a/x-pack/plugins/transform/public/app/common/pivot_aggs.ts +++ b/x-pack/plugins/transform/public/app/common/pivot_aggs.ts @@ -14,7 +14,7 @@ import type { Dictionary } from '../../../common/types/common'; import type { EsFieldName } from '../../../common/types/fields'; import type { PivotAgg, PivotSupportedAggs } from '../../../common/types/pivot_aggs'; import { PIVOT_SUPPORTED_AGGS } from '../../../common/types/pivot_aggs'; -import { isPopulatedObject } from '../../../common/utils/object_utils'; +import { isPopulatedObject } from '../../../common/shared_imports'; import { getAggFormConfig } from '../sections/create_transform/components/step_define/common/get_agg_form_config'; import { PivotAggsConfigFilter } from '../sections/create_transform/components/step_define/common/filter_agg/types'; @@ -166,11 +166,7 @@ export type PivotAggsConfigWithUiSupport = export function isPivotAggsConfigWithUiSupport(arg: unknown): arg is PivotAggsConfigWithUiSupport { return ( - isPopulatedObject(arg) && - arg.hasOwnProperty('agg') && - arg.hasOwnProperty('aggName') && - arg.hasOwnProperty('dropDownName') && - arg.hasOwnProperty('field') && + isPopulatedObject(arg, ['agg', 'aggName', 'dropDownName', 'field']) && isPivotSupportedAggs(arg.agg) ); } @@ -181,15 +177,12 @@ export function isPivotAggsConfigWithUiSupport(arg: unknown): arg is PivotAggsCo type PivotAggsConfigWithExtendedForm = PivotAggsConfigFilter; export function isPivotAggsWithExtendedForm(arg: unknown): arg is PivotAggsConfigWithExtendedForm { - return isPopulatedObject(arg) && arg.hasOwnProperty('AggFormComponent'); + return isPopulatedObject(arg, ['AggFormComponent']); } export function isPivotAggsConfigPercentiles(arg: unknown): arg is PivotAggsConfigPercentiles { return ( - isPopulatedObject(arg) && - arg.hasOwnProperty('agg') && - arg.hasOwnProperty('field') && - arg.hasOwnProperty('percents') && + isPopulatedObject(arg, ['agg', 'field', 'percents']) && arg.agg === PIVOT_SUPPORTED_AGGS.PERCENTILES ); } diff --git a/x-pack/plugins/transform/public/app/common/pivot_group_by.ts b/x-pack/plugins/transform/public/app/common/pivot_group_by.ts index fac0d88a84df7..0ad059fd29950 100644 --- a/x-pack/plugins/transform/public/app/common/pivot_group_by.ts +++ b/x-pack/plugins/transform/public/app/common/pivot_group_by.ts @@ -9,7 +9,7 @@ import { AggName } from '../../../common/types/aggregations'; import { Dictionary } from '../../../common/types/common'; import { EsFieldName } from '../../../common/types/fields'; import { GenericAgg } from '../../../common/types/pivot_group_by'; -import { isPopulatedObject } from '../../../common/utils/object_utils'; +import { isPopulatedObject } from '../../../common/shared_imports'; import { KBN_FIELD_TYPES } from '../../../../../../src/plugins/data/common'; import { PivotAggsConfigWithUiSupport } from './pivot_aggs'; @@ -84,30 +84,21 @@ export type PivotGroupByConfigDict = Dictionary; export function isGroupByDateHistogram(arg: unknown): arg is GroupByDateHistogram { return ( - isPopulatedObject(arg) && - arg.hasOwnProperty('agg') && - arg.hasOwnProperty('field') && - arg.hasOwnProperty('calendar_interval') && + isPopulatedObject(arg, ['agg', 'field', 'calendar_interval']) && arg.agg === PIVOT_SUPPORTED_GROUP_BY_AGGS.DATE_HISTOGRAM ); } export function isGroupByHistogram(arg: unknown): arg is GroupByHistogram { return ( - isPopulatedObject(arg) && - arg.hasOwnProperty('agg') && - arg.hasOwnProperty('field') && - arg.hasOwnProperty('interval') && + isPopulatedObject(arg, ['agg', 'field', 'interval']) && arg.agg === PIVOT_SUPPORTED_GROUP_BY_AGGS.HISTOGRAM ); } export function isGroupByTerms(arg: unknown): arg is GroupByTerms { return ( - isPopulatedObject(arg) && - arg.hasOwnProperty('agg') && - arg.hasOwnProperty('field') && - arg.agg === PIVOT_SUPPORTED_GROUP_BY_AGGS.TERMS + isPopulatedObject(arg, ['agg', 'field']) && arg.agg === PIVOT_SUPPORTED_GROUP_BY_AGGS.TERMS ); } @@ -124,5 +115,5 @@ export function getEsAggFromGroupByConfig(groupByConfig: GroupByConfigBase): Gen } export function isPivotAggConfigWithUiSupport(arg: unknown): arg is PivotAggsConfigWithUiSupport { - return isPopulatedObject(arg) && arg.hasOwnProperty('agg') && arg.hasOwnProperty('field'); + return isPopulatedObject(arg, ['agg', 'field']); } diff --git a/x-pack/plugins/transform/public/app/common/request.ts b/x-pack/plugins/transform/public/app/common/request.ts index 647d511cc4dcf..a7a3a91f9429b 100644 --- a/x-pack/plugins/transform/public/app/common/request.ts +++ b/x-pack/plugins/transform/public/app/common/request.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { DefaultOperator } from 'elasticsearch'; +import type { estypes } from '@elastic/elasticsearch'; import { HttpFetchError } from '../../../../../../src/core/public'; import type { IndexPattern } from '../../../../../../src/plugins/data/public'; @@ -17,7 +17,7 @@ import type { PutTransformsPivotRequestSchema, PutTransformsRequestSchema, } from '../../../common/api_schemas/transforms'; -import { isPopulatedObject } from '../../../common/utils/object_utils'; +import { isPopulatedObject } from '../../../common/shared_imports'; import { DateHistogramAgg, HistogramAgg, TermsAgg } from '../../../common/types/pivot_group_by'; import { isIndexPattern } from '../../../common/types/index_pattern'; @@ -39,7 +39,7 @@ import { export interface SimpleQuery { query_string: { query: string; - default_operator?: DefaultOperator; + default_operator?: estypes.DefaultOperator; }; } @@ -59,14 +59,13 @@ export function getPivotQuery(search: string | SavedSearchQuery): PivotQuery { } export function isSimpleQuery(arg: unknown): arg is SimpleQuery { - return isPopulatedObject(arg) && arg.hasOwnProperty('query_string'); + return isPopulatedObject(arg, ['query_string']); } export const matchAllQuery = { match_all: {} }; export function isMatchAllQuery(query: unknown): boolean { return ( - isPopulatedObject(query) && - query.hasOwnProperty('match_all') && + isPopulatedObject(query, ['match_all']) && typeof query.match_all === 'object' && query.match_all !== null && Object.keys(query.match_all).length === 0 @@ -101,7 +100,7 @@ export function getCombinedRuntimeMappings( combinedRuntimeMappings = { ...combinedRuntimeMappings, ...runtimeMappings }; } - if (isPopulatedObject(combinedRuntimeMappings)) { + if (isPopulatedObject(combinedRuntimeMappings)) { return combinedRuntimeMappings; } return undefined; diff --git a/x-pack/plugins/transform/public/app/hooks/__mocks__/use_api.ts b/x-pack/plugins/transform/public/app/hooks/__mocks__/use_api.ts index 7aaca793c2a1f..a9455877be429 100644 --- a/x-pack/plugins/transform/public/app/hooks/__mocks__/use_api.ts +++ b/x-pack/plugins/transform/public/app/hooks/__mocks__/use_api.ts @@ -5,6 +5,8 @@ * 2.0. */ +import { estypes } from '@elastic/elasticsearch'; + import { HttpFetchError } from 'kibana/public'; import { KBN_FIELD_TYPES } from '../../../../../../../src/plugins/data/public'; @@ -37,7 +39,6 @@ import type { PostTransformsUpdateResponseSchema, } from '../../../../common/api_schemas/update_transforms'; -import type { SearchResponse7 } from '../../../../common/shared_imports'; import { EsIndex } from '../../../../common/types/es_index'; import type { SavedSearchQuery } from '../use_search_items'; @@ -134,7 +135,7 @@ const apiFactory = () => ({ ): Promise { return Promise.resolve([]); }, - async esSearch(payload: any): Promise { + async esSearch(payload: any): Promise { return Promise.resolve({ hits: { hits: [], diff --git a/x-pack/plugins/transform/public/app/hooks/use_api.ts b/x-pack/plugins/transform/public/app/hooks/use_api.ts index f3c90a688453d..1abe2ed09444e 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_api.ts +++ b/x-pack/plugins/transform/public/app/hooks/use_api.ts @@ -7,6 +7,8 @@ import { useMemo } from 'react'; +import { estypes } from '@elastic/elasticsearch'; + import { HttpFetchError } from 'kibana/public'; import { KBN_FIELD_TYPES } from '../../../../../../src/plugins/data/public'; @@ -44,7 +46,6 @@ import type { GetTransformsStatsResponseSchema } from '../../../common/api_schem import { TransformId } from '../../../common/types/transform'; import { API_BASE_PATH } from '../../../common/constants'; import { EsIndex } from '../../../common/types/es_index'; -import type { SearchResponse7 } from '../../../common/shared_imports'; import { useAppDependencies } from '../app_dependencies'; @@ -187,7 +188,7 @@ export const useApi = () => { return e; } }, - async esSearch(payload: any): Promise { + async esSearch(payload: any): Promise { try { return await http.post(`${API_BASE_PATH}es_search`, { body: JSON.stringify(payload) }); } catch (e) { diff --git a/x-pack/plugins/transform/public/app/hooks/use_index_data.ts b/x-pack/plugins/transform/public/app/hooks/use_index_data.ts index e12aa78e33622..bb83de8e12004 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_index_data.ts +++ b/x-pack/plugins/transform/public/app/hooks/use_index_data.ts @@ -7,7 +7,8 @@ import { useEffect, useMemo } from 'react'; -import { EuiDataGridColumn } from '@elastic/eui'; +import type { estypes } from '@elastic/elasticsearch'; +import type { EuiDataGridColumn } from '@elastic/eui'; import { isEsSearchResponse, @@ -133,10 +134,14 @@ export const useIndexData = ( return; } - const docs = resp.hits.hits.map((d) => getProcessedFields(d.fields)); + const docs = resp.hits.hits.map((d) => getProcessedFields(d.fields ?? {})); - setRowCount(resp.hits.total.value); - setRowCountRelation(resp.hits.total.relation); + setRowCount(typeof resp.hits.total === 'number' ? resp.hits.total : resp.hits.total.value); + setRowCountRelation( + typeof resp.hits.total === 'number' + ? ('eq' as estypes.TotalHitsRelation) + : resp.hits.total.relation + ); setTableItems(docs); setStatus(INDEX_STATUS.LOADED); }; diff --git a/x-pack/plugins/transform/public/app/hooks/use_pivot_data.ts b/x-pack/plugins/transform/public/app/hooks/use_pivot_data.ts index 2477c005c936d..24c28effd12bc 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_pivot_data.ts +++ b/x-pack/plugins/transform/public/app/hooks/use_pivot_data.ts @@ -18,7 +18,11 @@ import type { PreviewMappingsProperties } from '../../../common/api_schemas/tran import { isPostTransformsPreviewResponseSchema } from '../../../common/api_schemas/type_guards'; import { getNestedProperty } from '../../../common/utils/object_utils'; -import { RenderCellValue, UseIndexDataReturnType, HITS_TOTAL_RELATION } from '../../shared_imports'; +import { + RenderCellValue, + UseIndexDataReturnType, + ES_CLIENT_TOTAL_HITS_RELATION, +} from '../../shared_imports'; import { getErrorMessage } from '../../../common/utils/errors'; import { useAppDependencies } from '../app_dependencies'; @@ -128,7 +132,7 @@ export const usePivotData = ( if (!validationStatus.isValid) { setTableItems([]); setRowCount(0); - setRowCountRelation(HITS_TOTAL_RELATION.EQ); + setRowCountRelation(ES_CLIENT_TOTAL_HITS_RELATION.EQ); setNoDataMessage(validationStatus.errorMessage!); return; } @@ -149,7 +153,7 @@ export const usePivotData = ( setErrorMessage(getErrorMessage(resp)); setTableItems([]); setRowCount(0); - setRowCountRelation(HITS_TOTAL_RELATION.EQ); + setRowCountRelation(ES_CLIENT_TOTAL_HITS_RELATION.EQ); setPreviewMappingsProperties({}); setStatus(INDEX_STATUS.ERROR); return; @@ -157,7 +161,7 @@ export const usePivotData = ( setTableItems(resp.preview); setRowCount(resp.preview.length); - setRowCountRelation(HITS_TOTAL_RELATION.EQ); + setRowCountRelation(ES_CLIENT_TOTAL_HITS_RELATION.EQ); setPreviewMappingsProperties(resp.generated_dest_index.mappings.properties); setStatus(INDEX_STATUS.LOADED); diff --git a/x-pack/plugins/transform/public/app/lib/authorization/components/common.ts b/x-pack/plugins/transform/public/app/lib/authorization/components/common.ts index 28e9f190a9108..5599e3f423277 100644 --- a/x-pack/plugins/transform/public/app/lib/authorization/components/common.ts +++ b/x-pack/plugins/transform/public/app/lib/authorization/components/common.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import { Privileges } from '../../../../../common/types/privileges'; -import { isPopulatedObject } from '../../../../../common/utils/object_utils'; +import { isPopulatedObject } from '../../../../../common/shared_imports'; export interface Capabilities { canGetTransform: boolean; @@ -22,10 +22,8 @@ export type Privilege = [string, string]; function isPrivileges(arg: unknown): arg is Privileges { return ( - isPopulatedObject(arg) && - arg.hasOwnProperty('hasAllPrivileges') && + isPopulatedObject(arg, ['hasAllPrivileges', 'missingPrivileges']) && typeof arg.hasAllPrivileges === 'boolean' && - arg.hasOwnProperty('missingPrivileges') && typeof arg.missingPrivileges === 'object' && arg.missingPrivileges !== null ); diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_runtime_mappings_editor/advanced_runtime_mappings_editor.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_runtime_mappings_editor/advanced_runtime_mappings_editor.tsx index 1e8397a4d9cc3..1e6e6a971a81a 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_runtime_mappings_editor/advanced_runtime_mappings_editor.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_runtime_mappings_editor/advanced_runtime_mappings_editor.tsx @@ -12,8 +12,9 @@ import { EuiCodeEditor } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { isRuntimeMappings } from '../../../../../../common/shared_imports'; + import { StepDefineFormHook } from '../step_define'; -import { isRuntimeMappings } from '../step_define/common/types'; export const AdvancedRuntimeMappingsEditor: FC = memo( ({ diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx index a7f2a3cd7178d..36bdca7921622 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx @@ -47,7 +47,7 @@ import { PutTransformsPivotRequestSchema, } from '../../../../../../common/api_schemas/transforms'; import type { RuntimeField } from '../../../../../../../../../src/plugins/data/common/index_patterns'; -import { isPopulatedObject } from '../../../../../../common/utils/object_utils'; +import { isPopulatedObject } from '../../../../../../common/shared_imports'; import { isLatestTransform } from '../../../../../../common/types/transform'; export interface StepDetailsExposedState { diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_agg_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_agg_form.tsx index 3b5d6e0e50497..9b349541a78a3 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_agg_form.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_agg_form.tsx @@ -16,7 +16,7 @@ import { getFilterAggTypeConfig } from '../config'; import type { FilterAggType, PivotAggsConfigFilter } from '../types'; import type { RuntimeMappings } from '../../types'; import { getKibanaFieldTypeFromEsType } from '../../get_pivot_dropdown_options'; -import { isPopulatedObject } from '../../../../../../../../../common/utils/object_utils'; +import { isPopulatedObject } from '../../../../../../../../../common/shared_imports'; /** * Resolves supported filters for provided field. diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_term_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_term_form.tsx index f2db6167c163c..358bb9dcafa96 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_term_form.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_term_form.tsx @@ -6,12 +6,16 @@ */ import React, { useCallback, useContext, useEffect, useState } from 'react'; -import { EuiComboBox, EuiFormRow } from '@elastic/eui'; +import { estypes } from '@elastic/elasticsearch'; +import { EuiComboBox, EuiComboBoxOptionOption, EuiFormRow } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { debounce } from 'lodash'; import useUpdateEffect from 'react-use/lib/useUpdateEffect'; import { i18n } from '@kbn/i18n'; -import { isEsSearchResponse } from '../../../../../../../../../common/api_schemas/type_guards'; +import { + isEsSearchResponseWithAggregations, + isMultiBucketAggregate, +} from '../../../../../../../../../common/api_schemas/type_guards'; import { useApi } from '../../../../../../../hooks'; import { CreateTransformWizardContext } from '../../../../wizard/wizard'; import { FilterAggConfigTerm } from '../types'; @@ -29,7 +33,7 @@ export const FilterTermForm: FilterAggConfigTerm['aggTypeConfig']['FilterAggForm const { indexPattern, runtimeMappings } = useContext(CreateTransformWizardContext); const toastNotifications = useToastNotifications(); - const [options, setOptions] = useState([]); + const [options, setOptions] = useState([]); const [isLoading, setIsLoading] = useState(true); /* eslint-disable-next-line react-hooks/exhaustive-deps */ @@ -62,7 +66,12 @@ export const FilterTermForm: FilterAggConfigTerm['aggTypeConfig']['FilterAggForm setIsLoading(false); - if (!isEsSearchResponse(response)) { + if ( + !( + isEsSearchResponseWithAggregations(response) && + isMultiBucketAggregate(response.aggregations.field_values) + ) + ) { toastNotifications.addWarning( i18n.translate('xpack.transform.agg.popoverForm.filerAgg.term.errorFetchSuggestions', { defaultMessage: 'Unable to fetch suggestions', @@ -72,9 +81,7 @@ export const FilterTermForm: FilterAggConfigTerm['aggTypeConfig']['FilterAggForm } setOptions( - response.aggregations.field_values.buckets.map( - (value: { key: string; doc_count: number }) => ({ label: value.key }) - ) + response.aggregations.field_values.buckets.map((value) => ({ label: value.key + '' })) ); }, 600), [selectedField] diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/get_pivot_dropdown_options.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/get_pivot_dropdown_options.ts index 8d85988424e27..957439810adc7 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/get_pivot_dropdown_options.ts +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/get_pivot_dropdown_options.ts @@ -13,6 +13,7 @@ import { } from '../../../../../../../../../../src/plugins/data/public'; import { getNestedProperty } from '../../../../../../../common/utils/object_utils'; +import { isRuntimeMappings } from '../../../../../../../common/shared_imports'; import { DropDownLabel, @@ -26,7 +27,6 @@ import { import { getDefaultAggregationConfig } from './get_default_aggregation_config'; import { getDefaultGroupByConfig } from './get_default_group_by_config'; import type { Field, StepDefineExposedState } from './types'; -import { isRuntimeMappings } from './types'; const illegalEsAggNameChars = /[[\]>]/g; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/types.test.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/types.test.ts deleted file mode 100644 index ec90d31a0d169..0000000000000 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/types.test.ts +++ /dev/null @@ -1,71 +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 { isRuntimeField, isRuntimeMappings } from './types'; - -describe('Transform: step_define type guards', () => { - it('isRuntimeField()', () => { - expect(isRuntimeField(1)).toBe(false); - expect(isRuntimeField(null)).toBe(false); - expect(isRuntimeField([])).toBe(false); - expect(isRuntimeField({})).toBe(false); - expect(isRuntimeField({ someAttribute: 'someValue' })).toBe(false); - expect(isRuntimeField({ type: 'wrong-type' })).toBe(false); - expect(isRuntimeField({ type: 'keyword', someAttribute: 'some value' })).toBe(false); - - expect(isRuntimeField({ type: 'keyword' })).toBe(true); - expect(isRuntimeField({ type: 'keyword', script: 'some script' })).toBe(true); - }); - - it('isRuntimeMappings()', () => { - expect(isRuntimeMappings(1)).toBe(false); - expect(isRuntimeMappings(null)).toBe(false); - expect(isRuntimeMappings([])).toBe(false); - expect(isRuntimeMappings({})).toBe(false); - expect(isRuntimeMappings({ someAttribute: 'someValue' })).toBe(false); - expect(isRuntimeMappings({ fieldName1: { type: 'keyword' }, fieldName2: 'someValue' })).toBe( - false - ); - expect( - isRuntimeMappings({ - fieldName1: { type: 'keyword' }, - fieldName2: { type: 'keyword', someAttribute: 'some value' }, - }) - ).toBe(false); - expect( - isRuntimeMappings({ - fieldName: { type: 'long', script: 1234 }, - }) - ).toBe(false); - expect( - isRuntimeMappings({ - fieldName: { type: 'long', script: { someAttribute: 'some value' } }, - }) - ).toBe(false); - expect( - isRuntimeMappings({ - fieldName: { type: 'long', script: { source: 1234 } }, - }) - ).toBe(false); - - expect(isRuntimeMappings({ fieldName: { type: 'keyword' } })).toBe(true); - expect( - isRuntimeMappings({ fieldName1: { type: 'keyword' }, fieldName2: { type: 'keyword' } }) - ).toBe(true); - expect( - isRuntimeMappings({ - fieldName1: { type: 'keyword' }, - fieldName2: { type: 'keyword', script: 'some script as script' }, - }) - ).toBe(true); - expect( - isRuntimeMappings({ - fieldName: { type: 'long', script: { source: 'some script as source' } }, - }) - ).toBe(true); - }); -}); diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/types.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/types.ts index 6b4ff0090a497..8b3b33fdde3ef 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/types.ts +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/types.ts @@ -24,7 +24,7 @@ import { } from '../../../../../../../common/types/transform'; import { LatestFunctionConfig } from '../../../../../../../common/api_schemas/transforms'; -import { isPopulatedObject } from '../../../../../../../common/utils/object_utils'; +import { isPopulatedObject } from '../../../../../../../common/shared_imports'; export interface ErrorMessage { query: string; @@ -72,30 +72,10 @@ export interface StepDefineExposedState { isRuntimeMappingsEditorEnabled: boolean; } -export function isRuntimeField(arg: unknown): arg is RuntimeField { - return ( - isPopulatedObject(arg) && - ((Object.keys(arg).length === 1 && arg.hasOwnProperty('type')) || - (Object.keys(arg).length === 2 && - arg.hasOwnProperty('type') && - arg.hasOwnProperty('script') && - (typeof arg.script === 'string' || - (isPopulatedObject(arg.script) && - Object.keys(arg.script).length === 1 && - arg.script.hasOwnProperty('source') && - typeof arg.script.source === 'string')))) && - RUNTIME_FIELD_TYPES.includes(arg.type as RuntimeType) - ); -} - -export function isRuntimeMappings(arg: unknown): arg is RuntimeMappings { - return isPopulatedObject(arg) && Object.values(arg).every((d) => isRuntimeField(d)); -} - export function isPivotPartialRequest(arg: unknown): arg is { pivot: PivotConfigDefinition } { - return isPopulatedObject(arg) && arg.hasOwnProperty('pivot'); + return isPopulatedObject(arg, ['pivot']); } export function isLatestPartialRequest(arg: unknown): arg is { latest: LatestFunctionConfig } { - return isPopulatedObject(arg) && arg.hasOwnProperty('latest'); + return isPopulatedObject(arg, ['latest']); } diff --git a/x-pack/plugins/transform/public/shared_imports.ts b/x-pack/plugins/transform/public/shared_imports.ts index ddf5cf7cb5cb1..edd27fd43c2af 100644 --- a/x-pack/plugins/transform/public/shared_imports.ts +++ b/x-pack/plugins/transform/public/shared_imports.ts @@ -15,7 +15,7 @@ export { UseIndexDataReturnType, EsSorting, RenderCellValue, - HITS_TOTAL_RELATION, + ES_CLIENT_TOTAL_HITS_RELATION, } from '../../ml/public'; import { XJson } from '../../../../src/plugins/es_ui_shared/public'; diff --git a/x-pack/plugins/transform/server/routes/api/transforms_nodes.ts b/x-pack/plugins/transform/server/routes/api/transforms_nodes.ts index afdcc93998303..c9a0795c32210 100644 --- a/x-pack/plugins/transform/server/routes/api/transforms_nodes.ts +++ b/x-pack/plugins/transform/server/routes/api/transforms_nodes.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { isPopulatedObject } from '../../../common/utils/object_utils'; +import { isPopulatedObject } from '../../../common/shared_imports'; import { RouteDependencies } from '../../types'; @@ -24,10 +24,7 @@ export const isNodes = (arg: unknown): arg is Nodes => { return ( isPopulatedObject(arg) && Object.values(arg).every( - (node) => - isPopulatedObject(node) && - {}.hasOwnProperty.call(node, NODE_ROLES) && - Array.isArray(node.roles) + (node) => isPopulatedObject(node, [NODE_ROLES]) && Array.isArray(node.roles) ) ); }; diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index b8bb77c8e4f09..8a4dc85f203e1 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -3459,7 +3459,6 @@ "timelion.help.configuration.valid.paragraph1Part2": "で Elasticsearch データソースの構成に関する詳細をご覧ください。", "timelion.help.configuration.valid.paragraph2": "すでにチャートが 1 つ表示されていますが、興味深いデータを得るにはいくつか調整が必要な可能性があります。", "timelion.help.configuration.valid.paragraph3": "これで、一定期間のデータポイントの数を示す折れ線グラフが表示されるはずです。", - "timelion.help.configuration.valid.timeRangeText": "Kibana ツールバーのタイムピッカーで可視化するデータを含む期間を選択します。上記のすべてまたは一部の時間範囲を含む時間範囲を選択するようにしてください。", "timelion.help.configuration.valid.timeRangeTitle": "時間範囲", "timelion.help.configuration.validTitle": "良いお知らせです。Elasticsearch が正しく構成されました!", "timelion.help.dataTransforming.functionReferenceLinkText": "機能リファレンス", @@ -3469,7 +3468,6 @@ "timelion.help.dataTransforming.paragraph4": "まぁまぁですが、これでは 0 から 1 までの値になってしまいます。パーセンテージに変換するには、100 を掛けます:{multiplyDataQuery}。", "timelion.help.dataTransforming.paragraph5": "これでトラフィックの何パーセントが米国からのものなのか分かり、一定期間内にどのように変化したのか見ることができます!Timelion には、{sum}、{subtract}、{multiply}、{divide} などのいくつもの演算機能が搭載されています。これらの多くが数列や数字を扱えます。また、{movingaverage}、{abs}、{derivative} といった他の便利な変換機能もあります。", "timelion.help.dataTransforming.paragraph6Part1": "構文を学んだところで、", - "timelion.help.dataTransforming.paragraph6Part2": "Timelion で利用できるすべての機能の使い方をご覧ください。Kibana ツールバーの \\{Docs\\} をクリックしていつでもリファレンスを参照することができます。このチュートリアルに戻るには、リファレンスの上にある \\{Tutorial\\} リンクをクリックします。", "timelion.help.dataTransformingTitle": "データの変換:お楽しみの始まりです!", "timelion.help.dontShowHelpButtonLabel": "今後表示しない", "timelion.help.expressions.examples.customStylingDescription": "{descriptionTitle}初めの数列を赤くし、2 つ目の数列に 1 ピクセル幅のバーを使用します。", @@ -3483,7 +3481,6 @@ "timelion.help.expressions.functionReferenceLinkText": "機能リファレンス", "timelion.help.expressions.paragraph1": "それぞれの式はデータソース関数で始まります。ここから、新しい関数をデータソースに追加して変換や強化ができます。", "timelion.help.expressions.paragraph2": "ところで、ここから先はデータの持ち主が一番よくご存知なのではないでしょうか。サンプルクエリをより有意義なものと自由に置き換えてみてください。", - "timelion.help.expressions.paragraph3": "Kibana ツールバーの {strongAdd} をクリックして、他のチャートをいくつか追加してみましょう。そして、チャートを選択して次の式の内の 1 つをコピーし、インプットバーに貼り付けて、Enter を押します。リセットして繰り返し、他の式を試してみましょう。", "timelion.help.expressions.paragraph4": "Timelion は、チャートの見た目をカスタマイズするための他のビュー変換機能も搭載しています。完全なリストは次のリソースをご覧ください", "timelion.help.expressions.strongAddText": "追加", "timelion.help.expressionsTitle": "式を使って式を定義", @@ -12756,7 +12753,6 @@ "xpack.maps.source.esSearch.topHitsSplitFieldLabel": "エンティティ", "xpack.maps.source.esSearch.topHitsSplitFieldSelectPlaceholder": "エンティティフィールドを選択", "xpack.maps.source.esSearch.useMVTVectorTiles": "ベクトルタイルを使用", - "xpack.maps.source.esSearch.useTopHitsLabel": "エンティティごとにトップヒットを表示。", "xpack.maps.source.esSearchDescription": "Elasticsearch の点、線、多角形", "xpack.maps.source.esSearchTitle": "ドキュメント", "xpack.maps.source.esSource.noGeoFieldErrorMessage": "インデックスパターン {indexPatternTitle} には現在ジオフィールド {geoField} が含まれていません", @@ -12800,8 +12796,6 @@ "xpack.maps.source.pewPewDescription": "ソースとデスティネーションの間の集約データパスです。", "xpack.maps.source.pewPewTitle": "ソースとデスティネーションの接続", "xpack.maps.source.urlLabel": "Url", - "xpack.maps.source.vetorSource.formatErrorMessage": "URL からベクターシェイプを取得できません:{format}", - "xpack.maps.source.vetorSource.requestFailedErrorMessage": "URL からベクターシェイプを取得できません:{fetchUrl}", "xpack.maps.source.wms.attributionLink": "属性テキストにはリンクが必要です", "xpack.maps.source.wms.attributionText": "属性 URL にはテキストが必要です", "xpack.maps.source.wms.getCapabilitiesButtonText": "負荷容量", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 825cf470d4406..cf8d2095fe6af 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -12920,7 +12920,6 @@ "xpack.maps.source.esSearch.topHitsSplitFieldLabel": "实体", "xpack.maps.source.esSearch.topHitsSplitFieldSelectPlaceholder": "选择实体字段", "xpack.maps.source.esSearch.useMVTVectorTiles": "使用矢量磁贴", - "xpack.maps.source.esSearch.useTopHitsLabel": "显示每个实体最高命中结果。", "xpack.maps.source.esSearchDescription": "Elasticsearch 的点、线和多边形", "xpack.maps.source.esSearchTitle": "文档", "xpack.maps.source.esSource.noGeoFieldErrorMessage": "索引模式“{indexPatternTitle}”不再包含地理字段 {geoField}", @@ -12964,8 +12963,6 @@ "xpack.maps.source.pewPewDescription": "源和目标之间的聚合数据路径", "xpack.maps.source.pewPewTitle": "源-目标连接", "xpack.maps.source.urlLabel": "URL", - "xpack.maps.source.vetorSource.formatErrorMessage": "无法从以下 URL 获取矢量形状:{format}", - "xpack.maps.source.vetorSource.requestFailedErrorMessage": "无法从以下 URL 获取矢量形状:{fetchUrl}", "xpack.maps.source.wms.attributionLink": "属性文本必须附带链接", "xpack.maps.source.wms.attributionText": "属性 url 必须附带文本", "xpack.maps.source.wms.getCapabilitiesButtonText": "加载功能", diff --git a/x-pack/scripts/jest.js b/x-pack/scripts/jest.js index 4c83073a559a4..2ea950e075c8c 100644 --- a/x-pack/scripts/jest.js +++ b/x-pack/scripts/jest.js @@ -5,4 +5,5 @@ * 2.0. */ +require('../../src/setup_node_env/ensure_node_preserve_symlinks'); require('@kbn/test').runJest(); diff --git a/x-pack/test/apm_api_integration/tests/correlations/errors_failed_transactions.ts b/x-pack/test/apm_api_integration/tests/correlations/errors_failed_transactions.ts new file mode 100644 index 0000000000000..80c2b98266248 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/correlations/errors_failed_transactions.ts @@ -0,0 +1,82 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { format } from 'url'; +import { APIReturnType } from '../../../../plugins/apm/public/services/rest/createCallApmApi'; +import archives_metadata from '../../common/fixtures/es_archiver/archives_metadata'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; + +export default function ApiTest({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const archiveName = 'apm_8.0.0'; + const range = archives_metadata[archiveName]; + + const url = format({ + pathname: `/api/apm/correlations/errors/failed_transactions`, + query: { + start: range.start, + end: range.end, + fieldNames: 'user_agent.name,user_agent.os.name,url.original', + }, + }); + registry.when( + 'correlations errors failed transactions without data', + { config: 'trial', archives: [] }, + () => { + it('handles the empty state', async () => { + const response = await supertest.get(url); + + expect(response.status).to.be(200); + expect(response.body.response).to.be(undefined); + }); + } + ); + + registry.when( + 'correlations errors failed transactions with data and default args', + { config: 'trial', archives: ['apm_8.0.0'] }, + () => { + type ResponseBody = APIReturnType<'GET /api/apm/correlations/errors/failed_transactions'>; + let response: { + status: number; + body: NonNullable; + }; + + before(async () => { + response = await supertest.get(url); + }); + + it('returns successfully', () => { + expect(response.status).to.eql(200); + }); + + it('returns significant terms', () => { + const { significantTerms } = response.body; + expect(significantTerms).to.have.length(2); + const sortedFieldNames = significantTerms.map(({ fieldName }) => fieldName).sort(); + expectSnapshot(sortedFieldNames).toMatchInline(` + Array [ + "user_agent.name", + "user_agent.name", + ] + `); + }); + + it('returns a distribution per term', () => { + const { significantTerms } = response.body; + expectSnapshot(significantTerms.map((term) => term.timeseries.length)).toMatchInline(` + Array [ + 31, + 31, + ] + `); + }); + } + ); +} diff --git a/x-pack/test/apm_api_integration/tests/correlations/errors_overall.ts b/x-pack/test/apm_api_integration/tests/correlations/errors_overall.ts new file mode 100644 index 0000000000000..206da2968b4c1 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/correlations/errors_overall.ts @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { format } from 'url'; +import { APIReturnType } from '../../../../plugins/apm/public/services/rest/createCallApmApi'; +import archives_metadata from '../../common/fixtures/es_archiver/archives_metadata'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; + +export default function ApiTest({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const archiveName = 'apm_8.0.0'; + const range = archives_metadata[archiveName]; + + const url = format({ + pathname: `/api/apm/correlations/errors/overall_timeseries`, + query: { + start: range.start, + end: range.end, + }, + }); + + registry.when( + 'correlations errors overall without data', + { config: 'trial', archives: [] }, + () => { + it('handles the empty state', async () => { + const response = await supertest.get(url); + + expect(response.status).to.be(200); + expect(response.body.response).to.be(undefined); + }); + } + ); + + registry.when( + 'correlations errors overall with data and default args', + { config: 'trial', archives: ['apm_8.0.0'] }, + () => { + type ResponseBody = APIReturnType<'GET /api/apm/correlations/errors/overall_timeseries'>; + let response: { + status: number; + body: NonNullable; + }; + + before(async () => { + response = await supertest.get(url); + }); + + it('returns successfully', () => { + expect(response.status).to.eql(200); + }); + + it('returns overall distribution', () => { + expectSnapshot(response.body?.overall?.timeseries.length).toMatchInline(`31`); + }); + } + ); +} diff --git a/x-pack/test/apm_api_integration/tests/correlations/latency_overall.ts b/x-pack/test/apm_api_integration/tests/correlations/latency_overall.ts new file mode 100644 index 0000000000000..0d79333faa9ef --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/correlations/latency_overall.ts @@ -0,0 +1,66 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { format } from 'url'; +import { APIReturnType } from '../../../../plugins/apm/public/services/rest/createCallApmApi'; +import archives_metadata from '../../common/fixtures/es_archiver/archives_metadata'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; + +export default function ApiTest({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const archiveName = 'apm_8.0.0'; + const range = archives_metadata[archiveName]; + + const url = format({ + pathname: `/api/apm/correlations/latency/overall_distribution`, + query: { + start: range.start, + end: range.end, + }, + }); + + registry.when( + 'correlations latency overall without data', + { config: 'trial', archives: [] }, + () => { + it('handles the empty state', async () => { + const response = await supertest.get(url); + + expect(response.status).to.be(200); + expect(response.body.response).to.be(undefined); + }); + } + ); + + registry.when( + 'correlations latency overall with data and default args', + { config: 'trial', archives: ['apm_8.0.0'] }, + () => { + type ResponseBody = APIReturnType<'GET /api/apm/correlations/latency/overall_distribution'>; + let response: { + status: number; + body: NonNullable; + }; + + before(async () => { + response = await supertest.get(url); + }); + + it('returns successfully', () => { + expect(response.status).to.eql(200); + }); + + it('returns overall distribution', () => { + expectSnapshot(response.body?.distributionInterval).toMatchInline(`238776`); + expectSnapshot(response.body?.maxLatency).toMatchInline(`3581640.00000003`); + expectSnapshot(response.body?.overallDistribution?.length).toMatchInline(`15`); + }); + } + ); +} diff --git a/x-pack/test/apm_api_integration/tests/correlations/latency_slow_transactions.ts b/x-pack/test/apm_api_integration/tests/correlations/latency_slow_transactions.ts new file mode 100644 index 0000000000000..d32beee0f31d5 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/correlations/latency_slow_transactions.ts @@ -0,0 +1,99 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { format } from 'url'; +import { APIReturnType } from '../../../../plugins/apm/public/services/rest/createCallApmApi'; +import archives_metadata from '../../common/fixtures/es_archiver/archives_metadata'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; + +export default function ApiTest({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const archiveName = 'apm_8.0.0'; + const range = archives_metadata[archiveName]; + + const url = format({ + pathname: `/api/apm/correlations/latency/slow_transactions`, + query: { + start: range.start, + end: range.end, + durationPercentile: 95, + fieldNames: 'user_agent.name,user_agent.os.name,url.original', + maxLatency: 3581640.00000003, + distributionInterval: 238776, + }, + }); + registry.when( + 'correlations latency slow transactions without data', + { config: 'trial', archives: [] }, + () => { + it('handles the empty state', async () => { + const response = await supertest.get(url); + + expect(response.status).to.be(200); + expect(response.body.response).to.be(undefined); + }); + } + ); + + registry.when( + 'correlations latency slow transactions with data and default args', + { config: 'trial', archives: ['apm_8.0.0'] }, + () => { + type ResponseBody = APIReturnType<'GET /api/apm/correlations/latency/slow_transactions'>; + let response: { + status: number; + body: NonNullable; + }; + + before(async () => { + response = await supertest.get(url); + }); + + it('returns successfully', () => { + expect(response.status).to.eql(200); + }); + + it('returns significant terms', () => { + const { significantTerms } = response.body; + expect(significantTerms).to.have.length(9); + const sortedFieldNames = significantTerms.map(({ fieldName }) => fieldName).sort(); + expectSnapshot(sortedFieldNames).toMatchInline(` + Array [ + "url.original", + "url.original", + "url.original", + "url.original", + "user_agent.name", + "user_agent.name", + "user_agent.name", + "user_agent.os.name", + "user_agent.os.name", + ] + `); + }); + + it('returns a distribution per term', () => { + const { significantTerms } = response.body; + expectSnapshot(significantTerms.map((term) => term.distribution.length)).toMatchInline(` + Array [ + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + ] + `); + }); + } + ); +} diff --git a/x-pack/test/apm_api_integration/tests/correlations/slow_transactions.ts b/x-pack/test/apm_api_integration/tests/correlations/slow_transactions.ts deleted file mode 100644 index c9686a8a9d5b0..0000000000000 --- a/x-pack/test/apm_api_integration/tests/correlations/slow_transactions.ts +++ /dev/null @@ -1,96 +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 expect from '@kbn/expect'; -import { format } from 'url'; -import { APIReturnType } from '../../../../plugins/apm/public/services/rest/createCallApmApi'; -import archives_metadata from '../../common/fixtures/es_archiver/archives_metadata'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; -import { registry } from '../../common/registry'; - -export default function ApiTest({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - const archiveName = 'apm_8.0.0'; - const range = archives_metadata[archiveName]; - - const url = format({ - pathname: `/api/apm/correlations/slow_transactions`, - query: { - start: range.start, - end: range.end, - durationPercentile: 95, - fieldNames: 'user_agent.name,user_agent.os.name,url.original', - }, - }); - - registry.when('without data', { config: 'trial', archives: [] }, () => { - it('handles the empty state', async () => { - const response = await supertest.get(url); - - expect(response.status).to.be(200); - expect(response.body.response).to.be(undefined); - }); - }); - - registry.when('with data and default args', { config: 'trial', archives: ['apm_8.0.0'] }, () => { - type ResponseBody = APIReturnType<'GET /api/apm/correlations/slow_transactions'>; - let response: { - status: number; - body: NonNullable; - }; - - before(async () => { - response = await supertest.get(url); - }); - - it('returns successfully', () => { - expect(response.status).to.eql(200); - }); - - it('returns significant terms', () => { - const significantTerms = response.body?.significantTerms as NonNullable< - typeof response.body.significantTerms - >; - expect(significantTerms).to.have.length(9); - const sortedFieldNames = significantTerms.map(({ fieldName }) => fieldName).sort(); - expectSnapshot(sortedFieldNames).toMatchInline(` - Array [ - "url.original", - "url.original", - "url.original", - "url.original", - "user_agent.name", - "user_agent.name", - "user_agent.name", - "user_agent.os.name", - "user_agent.os.name", - ] - `); - }); - - it('returns a distribution per term', () => { - expectSnapshot(response.body?.significantTerms?.map((term) => term.distribution.length)) - .toMatchInline(` - Array [ - 15, - 15, - 15, - 15, - 15, - 15, - 15, - 15, - 15, - ] - `); - }); - - it('returns overall distribution', () => { - expectSnapshot(response.body?.overall?.distribution.length).toMatchInline(`15`); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/tests/index.ts b/x-pack/test/apm_api_integration/tests/index.ts index 9f0f1b15c0580..7c69d5b996cea 100644 --- a/x-pack/test/apm_api_integration/tests/index.ts +++ b/x-pack/test/apm_api_integration/tests/index.ts @@ -24,8 +24,20 @@ export default function apmApiIntegrationTests(providerContext: FtrProviderConte loadTestFile(require.resolve('./alerts/chart_preview')); }); - describe('correlations/slow_transactions', function () { - loadTestFile(require.resolve('./correlations/slow_transactions')); + describe('correlations/latency_slow_transactions', function () { + loadTestFile(require.resolve('./correlations/latency_slow_transactions')); + }); + + describe('correlations/latency_overall', function () { + loadTestFile(require.resolve('./correlations/latency_overall')); + }); + + describe('correlations/errors_overall', function () { + loadTestFile(require.resolve('./correlations/errors_overall')); + }); + + describe('correlations/errors_failed_transactions', function () { + loadTestFile(require.resolve('./correlations/errors_failed_transactions')); }); describe('metrics_charts/metrics_charts', function () { diff --git a/x-pack/test/functional/apps/discover/__snapshots__/reporting.snap b/x-pack/test/functional/apps/discover/__snapshots__/reporting.snap index 8b33a1b41788d..005a5de016ff7 100644 --- a/x-pack/test/functional/apps/discover/__snapshots__/reporting.snap +++ b/x-pack/test/functional/apps/discover/__snapshots__/reporting.snap @@ -71,7 +71,7 @@ exports[`discover Discover CSV Export Generate CSV: new search generates a repor 24.5 ], \\"\\"type\\"\\": \\"\\"Point\\"\\" -}\\",\\"Abu Dhabi\\",\\"Angeldale, Oceanavigations, Microlutions\\",\\"Angeldale, Oceanavigations, Microlutions\\",\\"Jul 12, 2019 @ 00:00:00.000\\",716724,\\"sold_product_716724_23975, sold_product_716724_6338, sold_product_716724_14116, sold_product_716724_15290\\",\\"sold_product_716724_23975, sold_product_716724_6338, sold_product_716724_14116, sold_product_716724_15290\\",\\"79.99, 59.99, 21.99, 11.99\\",\\"79.99, 59.99, 21.99, 11.99\\",\\"Men's Shoes, Men's Clothing, Women's Accessories, Men's Accessories\\",\\"Men's Shoes, Men's Clothing, Women's Accessories, Men's Accessories\\",\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"0, 0, 0, 0\\",\\"0, 0, 0, 0\\",\\"Angeldale, Oceanavigations, Microlutions, Oceanavigations\\",\\"Angeldale, Oceanavigations, Microlutions, Oceanavigations\\",\\"42.39, 32.99, 10.34, 6.11\\",\\"79.99, 59.99, 21.99, 11.99\\",\\"23,975, 6,338, 14,116, 15,290\\",\\"Winter boots - cognac, Trenchcoat - black, Watch - black, Hat - light grey multicolor\\",\\"Winter boots - cognac, Trenchcoat - black, Watch - black, Hat - light grey multicolor\\",\\"1, 1, 1, 1\\",\\"ZO0687606876, ZO0290502905, ZO0126701267, ZO0308503085\\",\\"0, 0, 0, 0\\",\\"79.99, 59.99, 21.99, 11.99\\",\\"79.99, 59.99, 21.99, 11.99\\",\\"0, 0, 0, 0\\",\\"ZO0687606876, ZO0290502905, ZO0126701267, ZO0308503085\\",\\"173.96\\",\\"173.96\\",4,4,order,sultan +}\\",\\"Abu Dhabi\\",\\"Angeldale, Oceanavigations, Microlutions\\",\\"Angeldale, Oceanavigations, Microlutions\\",\\"Jul 12, 2019 @ 00:00:00.000\\",716724,\\"sold_product_716724_23975, sold_product_716724_6338, sold_product_716724_14116, sold_product_716724_15290\\",\\"sold_product_716724_23975, sold_product_716724_6338, sold_product_716724_14116, sold_product_716724_15290\\",\\"80, 60, 21.984, 11.992\\",\\"80, 60, 21.984, 11.992\\",\\"Men's Shoes, Men's Clothing, Women's Accessories, Men's Accessories\\",\\"Men's Shoes, Men's Clothing, Women's Accessories, Men's Accessories\\",\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"0, 0, 0, 0\\",\\"0, 0, 0, 0\\",\\"Angeldale, Oceanavigations, Microlutions, Oceanavigations\\",\\"Angeldale, Oceanavigations, Microlutions, Oceanavigations\\",\\"42.375, 33, 10.344, 6.109\\",\\"80, 60, 21.984, 11.992\\",\\"23,975, 6,338, 14,116, 15,290\\",\\"Winter boots - cognac, Trenchcoat - black, Watch - black, Hat - light grey multicolor\\",\\"Winter boots - cognac, Trenchcoat - black, Watch - black, Hat - light grey multicolor\\",\\"1, 1, 1, 1\\",\\"ZO0687606876, ZO0290502905, ZO0126701267, ZO0308503085\\",\\"0, 0, 0, 0\\",\\"80, 60, 21.984, 11.992\\",\\"80, 60, 21.984, 11.992\\",\\"0, 0, 0, 0\\",\\"ZO0687606876, ZO0290502905, ZO0126701267, ZO0308503085\\",174,174,4,4,order,sultan " `; @@ -83,6 +83,6 @@ exports[`discover Discover CSV Export Generate CSV: new search generates a repor 24.5 ], \\"\\"type\\"\\": \\"\\"Point\\"\\" -}\\",\\"Abu Dhabi\\",\\"Angeldale, Oceanavigations, Microlutions\\",\\"Angeldale, Oceanavigations, Microlutions\\",\\"Jul 12, 2019 @ 00:00:00.000\\",716724,\\"sold_product_716724_23975, sold_product_716724_6338, sold_product_716724_14116, sold_product_716724_15290\\",\\"sold_product_716724_23975, sold_product_716724_6338, sold_product_716724_14116, sold_product_716724_15290\\",\\"79.99, 59.99, 21.99, 11.99\\",\\"79.99, 59.99, 21.99, 11.99\\",\\"Men's Shoes, Men's Clothing, Women's Accessories, Men's Accessories\\",\\"Men's Shoes, Men's Clothing, Women's Accessories, Men's Accessories\\",\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"0, 0, 0, 0\\",\\"0, 0, 0, 0\\",\\"Angeldale, Oceanavigations, Microlutions, Oceanavigations\\",\\"Angeldale, Oceanavigations, Microlutions, Oceanavigations\\",\\"42.39, 32.99, 10.34, 6.11\\",\\"79.99, 59.99, 21.99, 11.99\\",\\"23,975, 6,338, 14,116, 15,290\\",\\"Winter boots - cognac, Trenchcoat - black, Watch - black, Hat - light grey multicolor\\",\\"Winter boots - cognac, Trenchcoat - black, Watch - black, Hat - light grey multicolor\\",\\"1, 1, 1, 1\\",\\"ZO0687606876, ZO0290502905, ZO0126701267, ZO0308503085\\",\\"0, 0, 0, 0\\",\\"79.99, 59.99, 21.99, 11.99\\",\\"79.99, 59.99, 21.99, 11.99\\",\\"0, 0, 0, 0\\",\\"ZO0687606876, ZO0290502905, ZO0126701267, ZO0308503085\\",\\"173.96\\",\\"173.96\\",4,4,order,sultan +}\\",\\"Abu Dhabi\\",\\"Angeldale, Oceanavigations, Microlutions\\",\\"Angeldale, Oceanavigations, Microlutions\\",\\"Jul 12, 2019 @ 00:00:00.000\\",716724,\\"sold_product_716724_23975, sold_product_716724_6338, sold_product_716724_14116, sold_product_716724_15290\\",\\"sold_product_716724_23975, sold_product_716724_6338, sold_product_716724_14116, sold_product_716724_15290\\",\\"80, 60, 21.984, 11.992\\",\\"80, 60, 21.984, 11.992\\",\\"Men's Shoes, Men's Clothing, Women's Accessories, Men's Accessories\\",\\"Men's Shoes, Men's Clothing, Women's Accessories, Men's Accessories\\",\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"0, 0, 0, 0\\",\\"0, 0, 0, 0\\",\\"Angeldale, Oceanavigations, Microlutions, Oceanavigations\\",\\"Angeldale, Oceanavigations, Microlutions, Oceanavigations\\",\\"42.375, 33, 10.344, 6.109\\",\\"80, 60, 21.984, 11.992\\",\\"23,975, 6,338, 14,116, 15,290\\",\\"Winter boots - cognac, Trenchcoat - black, Watch - black, Hat - light grey multicolor\\",\\"Winter boots - cognac, Trenchcoat - black, Watch - black, Hat - light grey multicolor\\",\\"1, 1, 1, 1\\",\\"ZO0687606876, ZO0290502905, ZO0126701267, ZO0308503085\\",\\"0, 0, 0, 0\\",\\"80, 60, 21.984, 11.992\\",\\"80, 60, 21.984, 11.992\\",\\"0, 0, 0, 0\\",\\"ZO0687606876, ZO0290502905, ZO0126701267, ZO0308503085\\",174,174,4,4,order,sultan " `; diff --git a/x-pack/test/functional/apps/discover/reporting.ts b/x-pack/test/functional/apps/discover/reporting.ts index 9acb4c311c113..d7dd961e2f103 100644 --- a/x-pack/test/functional/apps/discover/reporting.ts +++ b/x-pack/test/functional/apps/discover/reporting.ts @@ -21,8 +21,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await kibanaServer.uiSettings.update({ 'discover:searchFieldsFromSource': setValue }); }; - // Failing: See https://github.com/elastic/kibana/issues/95592 - describe.skip('Discover CSV Export', () => { + describe('Discover CSV Export', () => { before('initialize tests', async () => { log.debug('ReportingPage:initTests'); await esArchiver.load('reporting/ecommerce'); diff --git a/x-pack/test/functional/apps/security/users.js b/x-pack/test/functional/apps/security/users.js index 0cab12bc6672f..b471bbaed2fba 100644 --- a/x-pack/test/functional/apps/security/users.js +++ b/x-pack/test/functional/apps/security/users.js @@ -12,7 +12,8 @@ export default function ({ getService, getPageObjects }) { const config = getService('config'); const log = getService('log'); - describe('users', function () { + // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/96001 + describe.skip('users', function () { before(async () => { log.debug('users'); await PageObjects.settings.navigateTo(); @@ -90,7 +91,7 @@ export default function ({ getService, getPageObjects }) { expect(roles.apm_system.deprecated).to.be(false); expect(roles.apm_user.reserved).to.be(true); - expect(roles.apm_user.deprecated).to.be(false); + expect(roles.apm_user.deprecated).to.be(true); expect(roles.beats_admin.reserved).to.be(true); expect(roles.beats_admin.deprecated).to.be(false); diff --git a/x-pack/test/reporting_api_integration/reporting_and_security/__snapshots__/csv_searchsource_immediate.snap b/x-pack/test/reporting_api_integration/reporting_and_security/__snapshots__/csv_searchsource_immediate.snap index 2a96d72d197c1..1c753a1f62547 100644 --- a/x-pack/test/reporting_api_integration/reporting_and_security/__snapshots__/csv_searchsource_immediate.snap +++ b/x-pack/test/reporting_api_integration/reporting_and_security/__snapshots__/csv_searchsource_immediate.snap @@ -8,28 +8,28 @@ exports[`Reporting APIs CSV Generation from SearchSource Exports CSV with all fi 24.5 ], \\"\\"type\\"\\": \\"\\"Point\\"\\" -}\\",\\"Abu Dhabi\\",\\"Angeldale, Oceanavigations, Microlutions\\",\\"Angeldale, Oceanavigations, Microlutions\\",\\"Jul 12, 2019 @ 00:00:00.000\\",716724,\\"sold_product_716724_23975, sold_product_716724_6338, sold_product_716724_14116, sold_product_716724_15290\\",\\"sold_product_716724_23975, sold_product_716724_6338, sold_product_716724_14116, sold_product_716724_15290\\",\\"79.99, 59.99, 21.99, 11.99\\",\\"79.99, 59.99, 21.99, 11.99\\",\\"Men's Shoes, Men's Clothing, Women's Accessories, Men's Accessories\\",\\"Men's Shoes, Men's Clothing, Women's Accessories, Men's Accessories\\",\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"0, 0, 0, 0\\",\\"0, 0, 0, 0\\",\\"Angeldale, Oceanavigations, Microlutions, Oceanavigations\\",\\"Angeldale, Oceanavigations, Microlutions, Oceanavigations\\",\\"42.39, 32.99, 10.34, 6.11\\",\\"79.99, 59.99, 21.99, 11.99\\",\\"23,975, 6,338, 14,116, 15,290\\",\\"Winter boots - cognac, Trenchcoat - black, Watch - black, Hat - light grey multicolor\\",\\"Winter boots - cognac, Trenchcoat - black, Watch - black, Hat - light grey multicolor\\",\\"1, 1, 1, 1\\",\\"ZO0687606876, ZO0290502905, ZO0126701267, ZO0308503085\\",\\"0, 0, 0, 0\\",\\"79.99, 59.99, 21.99, 11.99\\",\\"79.99, 59.99, 21.99, 11.99\\",\\"0, 0, 0, 0\\",\\"ZO0687606876, ZO0290502905, ZO0126701267, ZO0308503085\\",\\"173.96\\",\\"173.96\\",4,4,order,sultan +}\\",\\"Abu Dhabi\\",\\"Angeldale, Oceanavigations, Microlutions\\",\\"Angeldale, Oceanavigations, Microlutions\\",\\"Jul 12, 2019 @ 00:00:00.000\\",716724,\\"sold_product_716724_23975, sold_product_716724_6338, sold_product_716724_14116, sold_product_716724_15290\\",\\"sold_product_716724_23975, sold_product_716724_6338, sold_product_716724_14116, sold_product_716724_15290\\",\\"80, 60, 21.984, 11.992\\",\\"80, 60, 21.984, 11.992\\",\\"Men's Shoes, Men's Clothing, Women's Accessories, Men's Accessories\\",\\"Men's Shoes, Men's Clothing, Women's Accessories, Men's Accessories\\",\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"0, 0, 0, 0\\",\\"0, 0, 0, 0\\",\\"Angeldale, Oceanavigations, Microlutions, Oceanavigations\\",\\"Angeldale, Oceanavigations, Microlutions, Oceanavigations\\",\\"42.375, 33, 10.344, 6.109\\",\\"80, 60, 21.984, 11.992\\",\\"23,975, 6,338, 14,116, 15,290\\",\\"Winter boots - cognac, Trenchcoat - black, Watch - black, Hat - light grey multicolor\\",\\"Winter boots - cognac, Trenchcoat - black, Watch - black, Hat - light grey multicolor\\",\\"1, 1, 1, 1\\",\\"ZO0687606876, ZO0290502905, ZO0126701267, ZO0308503085\\",\\"0, 0, 0, 0\\",\\"80, 60, 21.984, 11.992\\",\\"80, 60, 21.984, 11.992\\",\\"0, 0, 0, 0\\",\\"ZO0687606876, ZO0290502905, ZO0126701267, ZO0308503085\\",174,174,4,4,order,sultan 9gMtOW0BH63Xcmy432DJ,ecommerce,\\"-\\",\\"_doc\\",\\"Women's Shoes, Women's Clothing\\",\\"Women's Shoes, Women's Clothing\\",EUR,Pia,Pia,\\"Pia Richards\\",\\"Pia Richards\\",FEMALE,45,Richards,Richards,,Saturday,5,\\"pia@richards-family.zzz\\",Cannes,Europe,FR,\\"{ \\"\\"coordinates\\"\\": [ 7, 43.6 ], \\"\\"type\\"\\": \\"\\"Point\\"\\" -}\\",\\"Alpes-Maritimes\\",\\"Tigress Enterprises, Pyramidustries\\",\\"Tigress Enterprises, Pyramidustries\\",\\"Jul 12, 2019 @ 00:00:00.000\\",591503,\\"sold_product_591503_14761, sold_product_591503_11632\\",\\"sold_product_591503_14761, sold_product_591503_11632\\",\\"20.99, 20.99\\",\\"20.99, 20.99\\",\\"Women's Shoes, Women's Clothing\\",\\"Women's Shoes, Women's Clothing\\",\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"0, 0\\",\\"0, 0\\",\\"Tigress Enterprises, Pyramidustries\\",\\"Tigress Enterprises, Pyramidustries\\",\\"10.7, 9.87\\",\\"20.99, 20.99\\",\\"14,761, 11,632\\",\\"Classic heels - blue, Summer dress - coral/pink\\",\\"Classic heels - blue, Summer dress - coral/pink\\",\\"1, 1\\",\\"ZO0006400064, ZO0150601506\\",\\"0, 0\\",\\"20.99, 20.99\\",\\"20.99, 20.99\\",\\"0, 0\\",\\"ZO0006400064, ZO0150601506\\",\\"41.98\\",\\"41.98\\",2,2,order,pia +}\\",\\"Alpes-Maritimes\\",\\"Tigress Enterprises, Pyramidustries\\",\\"Tigress Enterprises, Pyramidustries\\",\\"Jul 12, 2019 @ 00:00:00.000\\",591503,\\"sold_product_591503_14761, sold_product_591503_11632\\",\\"sold_product_591503_14761, sold_product_591503_11632\\",\\"20.984, 20.984\\",\\"20.984, 20.984\\",\\"Women's Shoes, Women's Clothing\\",\\"Women's Shoes, Women's Clothing\\",\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"0, 0\\",\\"0, 0\\",\\"Tigress Enterprises, Pyramidustries\\",\\"Tigress Enterprises, Pyramidustries\\",\\"10.703, 9.867\\",\\"20.984, 20.984\\",\\"14,761, 11,632\\",\\"Classic heels - blue, Summer dress - coral/pink\\",\\"Classic heels - blue, Summer dress - coral/pink\\",\\"1, 1\\",\\"ZO0006400064, ZO0150601506\\",\\"0, 0\\",\\"20.984, 20.984\\",\\"20.984, 20.984\\",\\"0, 0\\",\\"ZO0006400064, ZO0150601506\\",\\"41.969\\",\\"41.969\\",2,2,order,pia BgMtOW0BH63Xcmy432LJ,ecommerce,\\"-\\",\\"_doc\\",\\"Women's Clothing\\",\\"Women's Clothing\\",EUR,Brigitte,Brigitte,\\"Brigitte Meyer\\",\\"Brigitte Meyer\\",FEMALE,12,Meyer,Meyer,,Saturday,5,\\"brigitte@meyer-family.zzz\\",\\"New York\\",\\"North America\\",US,\\"{ \\"\\"coordinates\\"\\": [ -74, 40.8 ], \\"\\"type\\"\\": \\"\\"Point\\"\\" -}\\",\\"New York\\",\\"Spherecords, Tigress Enterprises\\",\\"Spherecords, Tigress Enterprises\\",\\"Jul 12, 2019 @ 00:00:00.000\\",591709,\\"sold_product_591709_20734, sold_product_591709_7539\\",\\"sold_product_591709_20734, sold_product_591709_7539\\",\\"7.99, 32.99\\",\\"7.99, 32.99\\",\\"Women's Clothing, Women's Clothing\\",\\"Women's Clothing, Women's Clothing\\",\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"0, 0\\",\\"0, 0\\",\\"Spherecords, Tigress Enterprises\\",\\"Spherecords, Tigress Enterprises\\",\\"3.6, 17.48\\",\\"7.99, 32.99\\",\\"20,734, 7,539\\",\\"Basic T-shirt - dark blue, Summer dress - scarab\\",\\"Basic T-shirt - dark blue, Summer dress - scarab\\",\\"1, 1\\",\\"ZO0638206382, ZO0038800388\\",\\"0, 0\\",\\"7.99, 32.99\\",\\"7.99, 32.99\\",\\"0, 0\\",\\"ZO0638206382, ZO0038800388\\",\\"40.98\\",\\"40.98\\",2,2,order,brigitte +}\\",\\"New York\\",\\"Spherecords, Tigress Enterprises\\",\\"Spherecords, Tigress Enterprises\\",\\"Jul 12, 2019 @ 00:00:00.000\\",591709,\\"sold_product_591709_20734, sold_product_591709_7539\\",\\"sold_product_591709_20734, sold_product_591709_7539\\",\\"7.988, 33\\",\\"7.988, 33\\",\\"Women's Clothing, Women's Clothing\\",\\"Women's Clothing, Women's Clothing\\",\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"0, 0\\",\\"0, 0\\",\\"Spherecords, Tigress Enterprises\\",\\"Spherecords, Tigress Enterprises\\",\\"3.6, 17.484\\",\\"7.988, 33\\",\\"20,734, 7,539\\",\\"Basic T-shirt - dark blue, Summer dress - scarab\\",\\"Basic T-shirt - dark blue, Summer dress - scarab\\",\\"1, 1\\",\\"ZO0638206382, ZO0038800388\\",\\"0, 0\\",\\"7.988, 33\\",\\"7.988, 33\\",\\"0, 0\\",\\"ZO0638206382, ZO0038800388\\",\\"40.969\\",\\"40.969\\",2,2,order,brigitte KQMtOW0BH63Xcmy432LJ,ecommerce,\\"-\\",\\"_doc\\",\\"Men's Clothing\\",\\"Men's Clothing\\",EUR,Abd,Abd,\\"Abd Mccarthy\\",\\"Abd Mccarthy\\",MALE,52,Mccarthy,Mccarthy,,Saturday,5,\\"abd@mccarthy-family.zzz\\",Cairo,Africa,EG,\\"{ \\"\\"coordinates\\"\\": [ 31.3, 30.1 ], \\"\\"type\\"\\": \\"\\"Point\\"\\" -}\\",\\"Cairo Governorate\\",\\"Oceanavigations, Elitelligence\\",\\"Oceanavigations, Elitelligence\\",\\"Jul 12, 2019 @ 00:00:00.000\\",590937,\\"sold_product_590937_14438, sold_product_590937_23607\\",\\"sold_product_590937_14438, sold_product_590937_23607\\",\\"28.99, 12.99\\",\\"28.99, 12.99\\",\\"Men's Clothing, Men's Clothing\\",\\"Men's Clothing, Men's Clothing\\",\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"0, 0\\",\\"0, 0\\",\\"Oceanavigations, Elitelligence\\",\\"Oceanavigations, Elitelligence\\",\\"13.34, 6.11\\",\\"28.99, 12.99\\",\\"14,438, 23,607\\",\\"Jumper - dark grey multicolor, Print T-shirt - black\\",\\"Jumper - dark grey multicolor, Print T-shirt - black\\",\\"1, 1\\",\\"ZO0297602976, ZO0565605656\\",\\"0, 0\\",\\"28.99, 12.99\\",\\"28.99, 12.99\\",\\"0, 0\\",\\"ZO0297602976, ZO0565605656\\",\\"41.98\\",\\"41.98\\",2,2,order,abd +}\\",\\"Cairo Governorate\\",\\"Oceanavigations, Elitelligence\\",\\"Oceanavigations, Elitelligence\\",\\"Jul 12, 2019 @ 00:00:00.000\\",590937,\\"sold_product_590937_14438, sold_product_590937_23607\\",\\"sold_product_590937_14438, sold_product_590937_23607\\",\\"28.984, 12.992\\",\\"28.984, 12.992\\",\\"Men's Clothing, Men's Clothing\\",\\"Men's Clothing, Men's Clothing\\",\\"Dec 31, 2016 @ 00:00:00.000, Dec 31, 2016 @ 00:00:00.000\\",\\"0, 0\\",\\"0, 0\\",\\"Oceanavigations, Elitelligence\\",\\"Oceanavigations, Elitelligence\\",\\"13.344, 6.109\\",\\"28.984, 12.992\\",\\"14,438, 23,607\\",\\"Jumper - dark grey multicolor, Print T-shirt - black\\",\\"Jumper - dark grey multicolor, Print T-shirt - black\\",\\"1, 1\\",\\"ZO0297602976, ZO0565605656\\",\\"0, 0\\",\\"28.984, 12.992\\",\\"28.984, 12.992\\",\\"0, 0\\",\\"ZO0297602976, ZO0565605656\\",\\"41.969\\",\\"41.969\\",2,2,order,abd " `; diff --git a/x-pack/test/reporting_api_integration/reporting_and_security/csv_searchsource_immediate.ts b/x-pack/test/reporting_api_integration/reporting_and_security/csv_searchsource_immediate.ts index ffaa4cb2f8fb6..27c6a05f740bf 100644 --- a/x-pack/test/reporting_api_integration/reporting_and_security/csv_searchsource_immediate.ts +++ b/x-pack/test/reporting_api_integration/reporting_and_security/csv_searchsource_immediate.ts @@ -31,8 +31,7 @@ export default function ({ getService }: FtrProviderContext) { }, }; - // Failing: See https://github.com/elastic/kibana/issues/95594 - describe.skip('CSV Generation from SearchSource', () => { + describe('CSV Generation from SearchSource', () => { before(async () => { await kibanaServer.uiSettings.update({ 'csv:quoteValues': false, diff --git a/yarn.lock b/yarn.lock index e3764a1e0a6a9..3a2440f10d6b5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1197,6 +1197,16 @@ resolved "https://registry.yarnpkg.com/@bazel/ibazel/-/ibazel-0.14.0.tgz#86fa0002bed2ce1123b7ad98d4dd4623a0d93244" integrity sha512-s0gyec6lArcRDwVfIP6xpY8iEaFpzrSpyErSppd3r2O49pOEg7n6HGS/qJ8ncvme56vrDk6crl/kQ6VAdEO+rg== +"@bazel/typescript@^3.2.3": + version "3.2.3" + resolved "https://registry.yarnpkg.com/@bazel/typescript/-/typescript-3.2.3.tgz#6e40bdb7c5294e588bac3b7d1269e58b98a1856c" + integrity sha512-Q1Yin/AYdh9yrkSJo3H6nVn6mMaohr5syjLd0Df0w7WI4zerdJTxrY5nhoWZwO/S1rPj8/MedDwZudCqPDeDMA== + dependencies: + protobufjs "6.8.8" + semver "5.6.0" + source-map-support "0.5.9" + tsutils "2.27.2" + "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" @@ -1375,7 +1385,7 @@ utility-types "^3.10.0" uuid "^3.3.2" -"@elastic/datemath@link:packages/elastic-datemath": +"@elastic/datemath@link:bazel-bin/packages/elastic-datemath/npm_module": version "0.0.0" uid "" @@ -3441,6 +3451,59 @@ dependencies: "@babel/runtime" "^7.0.0" +"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" + integrity sha1-m4sMxmPWaafY9vXQiToU00jzD78= + +"@protobufjs/base64@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735" + integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg== + +"@protobufjs/codegen@^2.0.4": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb" + integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg== + +"@protobufjs/eventemitter@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70" + integrity sha1-NVy8mLr61ZePntCV85diHx0Ga3A= + +"@protobufjs/fetch@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45" + integrity sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU= + dependencies: + "@protobufjs/aspromise" "^1.1.1" + "@protobufjs/inquire" "^1.1.0" + +"@protobufjs/float@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1" + integrity sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E= + +"@protobufjs/inquire@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089" + integrity sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik= + +"@protobufjs/path@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d" + integrity sha1-bMKyDFya1q0NzP0hynZz2Nf79o0= + +"@protobufjs/pool@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54" + integrity sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q= + +"@protobufjs/utf8@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" + integrity sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA= + "@reach/router@^1.3.3": version "1.3.4" resolved "https://registry.yarnpkg.com/@reach/router/-/router-1.3.4.tgz#d2574b19370a70c80480ed91f3da840136d10f8c" @@ -5181,6 +5244,11 @@ resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.159.tgz#61089719dc6fdd9c5cb46efc827f2571d1517065" integrity sha512-gF7A72f7WQN33DpqOWw9geApQPh4M3PxluMtaHxWHXEGSN12/WbcEk/eNSqWNQcQhF66VSZ06vCF94CrHwXJDg== +"@types/long@^4.0.0": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9" + integrity sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w== + "@types/lru-cache@^5.1.0": version "5.1.0" resolved "https://registry.yarnpkg.com/@types/lru-cache/-/lru-cache-5.1.0.tgz#57f228f2b80c046b4a1bd5cac031f81f207f4f03" @@ -5330,7 +5398,7 @@ dependencies: "@types/node" "*" -"@types/node@*", "@types/node@14.14.14", "@types/node@8.10.54", "@types/node@>= 8", "@types/node@>=8.9.0", "@types/node@^12.0.2": +"@types/node@*", "@types/node@14.14.14", "@types/node@8.10.54", "@types/node@>= 8", "@types/node@>=8.9.0", "@types/node@^10.1.0", "@types/node@^12.0.2": version "14.14.14" resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.14.tgz#f7fd5f3cc8521301119f63910f0fb965c7d761ae" integrity sha512-UHnOPWVWV1z+VV8k6L1HhG7UbGBgIdghqF3l9Ny9ApPghbjICXkUJSd/b9gOgQfjM1r+37cipdw/HJ3F6ICEnQ== @@ -10044,15 +10112,14 @@ concat-stream@~2.0.0: typedarray "^0.0.6" concaveman@*: - version "1.1.1" - resolved "https://registry.yarnpkg.com/concaveman/-/concaveman-1.1.1.tgz#6c2482580b2523cef82fc2bec00a0415e6e68162" - integrity sha1-bCSCWAslI874L8K+wAoEFebmgWI= + version "1.2.0" + resolved "https://registry.yarnpkg.com/concaveman/-/concaveman-1.2.0.tgz#4340f27c08a11bdc1d5fac13476862a2ab09b703" + integrity sha512-OcqechF2/kubbffomKqjGEkb0ndlYhEbmyg/fxIGqdfYp5AZjD2Kl5hc97Hh3ngEuHU2314Z4KDbxL7qXGWrQQ== dependencies: - monotone-convex-hull-2d "^1.0.1" point-in-polygon "^1.0.1" - rbush "^2.0.1" - robust-orientation "^1.1.3" - tinyqueue "^1.1.0" + rbush "^3.0.0" + robust-predicates "^2.0.4" + tinyqueue "^2.0.3" config-chain@^1.1.12: version "1.1.12" @@ -20241,13 +20308,6 @@ monocle-ts@^1.0.0: resolved "https://registry.yarnpkg.com/monocle-ts/-/monocle-ts-1.7.1.tgz#03a615938aa90983a4fa29749969d30f72d80ba1" integrity sha512-X9OzpOyd/R83sYex8NYpJjUzi/MLQMvGNVfxDYiIvs+QMXMEUDwR61MQoARFN10Cqz5h/mbFSPnIQNUIGhYd2Q== -monotone-convex-hull-2d@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/monotone-convex-hull-2d/-/monotone-convex-hull-2d-1.0.1.tgz#47f5daeadf3c4afd37764baa1aa8787a40eee08c" - integrity sha1-R/Xa6t88Sv03dkuqGqh4ekDu4Iw= - dependencies: - robust-orientation "^1.1.3" - moo@^0.4.3: version "0.4.3" resolved "https://registry.yarnpkg.com/moo/-/moo-0.4.3.tgz#3f847a26f31cf625a956a87f2b10fbc013bfd10e" @@ -22923,6 +22983,25 @@ proto-list@~1.2.1: resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" integrity sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk= +protobufjs@6.8.8: + version "6.8.8" + resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.8.8.tgz#c8b4f1282fd7a90e6f5b109ed11c84af82908e7c" + integrity sha512-AAmHtD5pXgZfi7GMpllpO3q1Xw1OYldr+dMUlAnffGTAhqkg72WdmSY71uKBF/JuyiKs8psYbtKrhi0ASCD8qw== + dependencies: + "@protobufjs/aspromise" "^1.1.2" + "@protobufjs/base64" "^1.1.2" + "@protobufjs/codegen" "^2.0.4" + "@protobufjs/eventemitter" "^1.1.0" + "@protobufjs/fetch" "^1.1.0" + "@protobufjs/float" "^1.0.2" + "@protobufjs/inquire" "^1.1.0" + "@protobufjs/path" "^1.1.2" + "@protobufjs/pool" "^1.1.0" + "@protobufjs/utf8" "^1.1.0" + "@types/long" "^4.0.0" + "@types/node" "^10.1.0" + long "^4.0.0" + protocol-buffers-schema@^3.3.1: version "3.3.2" resolved "https://registry.yarnpkg.com/protocol-buffers-schema/-/protocol-buffers-schema-3.3.2.tgz#00434f608b4e8df54c59e070efeefc37fb4bb859" @@ -23143,11 +23222,6 @@ quick-lru@^4.0.1: resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f" integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g== -quickselect@^1.0.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/quickselect/-/quickselect-1.1.1.tgz#852e412ce418f237ad5b660d70cffac647ae94c2" - integrity sha512-qN0Gqdw4c4KGPsBOQafj6yj/PA6c/L63f6CaZ/DCF/xF4Esu3jVmKLUDYxghFx8Kb/O7y9tI7x2RjTSXwdK1iQ== - quickselect@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/quickselect/-/quickselect-2.0.0.tgz#f19680a486a5eefb581303e023e98faaf25dd018" @@ -23258,14 +23332,7 @@ raw-loader@^4.0.1: loader-utils "^2.0.0" schema-utils "^2.6.5" -rbush@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/rbush/-/rbush-2.0.2.tgz#bb6005c2731b7ba1d5a9a035772927d16a614605" - integrity sha512-XBOuALcTm+O/H8G90b6pzu6nX6v2zCKiFG4BJho8a+bY6AER6t8uQUZdi5bomQc0AprCWhEGa7ncAbbRap0bRA== - dependencies: - quickselect "^1.0.1" - -rbush@^3.0.1: +rbush@^3.0.0, rbush@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/rbush/-/rbush-3.0.1.tgz#5fafa8a79b3b9afdfe5008403a720cc1de882ecf" integrity sha512-XRaVO0YecOpEuIvbhbpTrZgoiI6xBlz6hnlr6EHhd+0x9ase6EmeN+hdwwUaJvLcsFFQ8iWVF1GAK1yB0BWi0w== @@ -25131,33 +25198,10 @@ rison-node@1.0.2: resolved "https://registry.yarnpkg.com/rison-node/-/rison-node-1.0.2.tgz#b7b5f37f39f5ae2a51a973a33c9aa17239a33e4b" integrity sha1-t7Xzfzn1ripRqXOjPJqhcjmjPks= -robust-orientation@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/robust-orientation/-/robust-orientation-1.1.3.tgz#daff5b00d3be4e60722f0e9c0156ef967f1c2049" - integrity sha1-2v9bANO+TmByLw6cAVbvln8cIEk= - dependencies: - robust-scale "^1.0.2" - robust-subtract "^1.0.0" - robust-sum "^1.0.0" - two-product "^1.0.2" - -robust-scale@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/robust-scale/-/robust-scale-1.0.2.tgz#775132ed09542d028e58b2cc79c06290bcf78c32" - integrity sha1-d1Ey7QlULQKOWLLMecBikLz3jDI= - dependencies: - two-product "^1.0.2" - two-sum "^1.0.0" - -robust-subtract@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/robust-subtract/-/robust-subtract-1.0.0.tgz#e0b164e1ed8ba4e3a5dda45a12038348dbed3e9a" - integrity sha1-4LFk4e2LpOOl3aRaEgODSNvtPpo= - -robust-sum@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/robust-sum/-/robust-sum-1.0.0.tgz#16646e525292b4d25d82757a286955e0bbfa53d9" - integrity sha1-FmRuUlKStNJdgnV6KGlV4Lv6U9k= +robust-predicates@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/robust-predicates/-/robust-predicates-2.0.4.tgz#0a2367a93abd99676d075981707f29cfb402248b" + integrity sha512-l4NwboJM74Ilm4VKfbAtFeGq7aEjWL+5kVFcmgFA2MrdnQWx9iE/tUGvxY5HyMI7o/WpSIUFLbC5fbeaHgSCYg== rollup@^0.25.8: version "0.25.8" @@ -25480,6 +25524,11 @@ semver-greatest-satisfied-range@^1.1.0: resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== +semver@5.6.0: + version "5.6.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" + integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg== + semver@7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" @@ -25967,6 +26016,14 @@ source-map-resolve@^0.6.0: atob "^2.1.2" decode-uri-component "^0.2.0" +source-map-support@0.5.9: + version "0.5.9" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.9.tgz#41bc953b2534267ea2d605bccfa7bfa3111ced5f" + integrity sha512-gR6Rw4MvUlYy83vP0vxoVNzM6t8MUXqNuRsuBmBHQDu1Fh6X015FrLdgoDKcNdkwGubozq0P4N0Q37UyFVr1EA== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + source-map-support@^0.3.2: version "0.3.3" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.3.3.tgz#34900977d5ba3f07c7757ee72e73bb1a9b53754f" @@ -27512,11 +27569,6 @@ tinygradient@0.4.3: "@types/tinycolor2" "^1.4.0" tinycolor2 "^1.0.0" -tinyqueue@^1.1.0: - version "1.2.3" - resolved "https://registry.yarnpkg.com/tinyqueue/-/tinyqueue-1.2.3.tgz#b6a61de23060584da29f82362e45df1ec7353f3d" - integrity sha512-Qz9RgWuO9l8lT+Y9xvbzhPT2efIUIFd69N7eF7tJ9lnQl0iLj1M7peK7IoUGZL9DJHw9XftqLreccfxcQgYLxA== - tinyqueue@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/tinyqueue/-/tinyqueue-2.0.3.tgz#64d8492ebf39e7801d7bd34062e29b45b2035f08" @@ -27907,6 +27959,13 @@ tslib@~2.1.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a" integrity sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A== +tsutils@2.27.2: + version "2.27.2" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.27.2.tgz#60ba88a23d6f785ec4b89c6e8179cac9b431f1c7" + integrity sha512-qf6rmT84TFMuxAKez2pIfR8UCai49iQsfB7YWVjV1bKpy/d0PWT5rEOSM6La9PiHZ0k1RRZQiwVdVJfQ3BPHgg== + dependencies: + tslib "^1.8.1" + tsutils@^3.17.1: version "3.17.1" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759" @@ -27936,16 +27995,6 @@ tweetnacl@^0.14.3, tweetnacl@~0.14.0: resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= -two-product@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/two-product/-/two-product-1.0.2.tgz#67d95d4b257a921e2cb4bd7af9511f9088522eaa" - integrity sha1-Z9ldSyV6kh4stL16+VEfkIhSLqo= - -two-sum@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/two-sum/-/two-sum-1.0.0.tgz#31d3f32239e4f731eca9df9155e2b297f008ab64" - integrity sha1-MdPzIjnk9zHsqd+RVeKyl/AIq2Q= - type-check@~0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72"